diff --git a/.cproject b/.cproject new file mode 100644 index 0000000..6d33835 --- /dev/null +++ b/.cproject @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.settings/org.eclipse.cdt.codan.core.prefs b/.settings/org.eclipse.cdt.codan.core.prefs new file mode 100644 index 0000000..77386c2 --- /dev/null +++ b/.settings/org.eclipse.cdt.codan.core.prefs @@ -0,0 +1,67 @@ +eclipse.preferences.version=1 +org.eclipse.cdt.codan.checkers.errnoreturn=Warning +org.eclipse.cdt.codan.checkers.errnoreturn.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},implicit\=>false} +org.eclipse.cdt.codan.checkers.errreturnvalue=Error +org.eclipse.cdt.codan.checkers.errreturnvalue.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.checkers.noreturn=Error +org.eclipse.cdt.codan.checkers.noreturn.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},implicit\=>false} +org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation=Error +org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.AmbiguousProblem=Error +org.eclipse.cdt.codan.internal.checkers.AmbiguousProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.AssignmentInConditionProblem=Warning +org.eclipse.cdt.codan.internal.checkers.AssignmentInConditionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.AssignmentToItselfProblem=Error +org.eclipse.cdt.codan.internal.checkers.AssignmentToItselfProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem=Warning +org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},no_break_comment\=>"no break",last_case_param\=>false,empty_case_param\=>false} +org.eclipse.cdt.codan.internal.checkers.CatchByReference=Warning +org.eclipse.cdt.codan.internal.checkers.CatchByReference.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},unknown\=>false,exceptions\=>()} +org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem=Error +org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization=Warning +org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},skip\=>true} +org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem=Error +org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.FunctionResolutionProblem=Error +org.eclipse.cdt.codan.internal.checkers.FunctionResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.InvalidArguments=Error +org.eclipse.cdt.codan.internal.checkers.InvalidArguments.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.InvalidTemplateArgumentsProblem=Error +org.eclipse.cdt.codan.internal.checkers.InvalidTemplateArgumentsProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.LabelStatementNotFoundProblem=Error +org.eclipse.cdt.codan.internal.checkers.LabelStatementNotFoundProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.MemberDeclarationNotFoundProblem=Error +org.eclipse.cdt.codan.internal.checkers.MemberDeclarationNotFoundProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.MethodResolutionProblem=Error +org.eclipse.cdt.codan.internal.checkers.MethodResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.NamingConventionFunctionChecker=-Info +org.eclipse.cdt.codan.internal.checkers.NamingConventionFunctionChecker.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},pattern\=>"^[a-z]",macro\=>true,exceptions\=>()} +org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem=Warning +org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.OverloadProblem=Error +org.eclipse.cdt.codan.internal.checkers.OverloadProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.RedeclarationProblem=Error +org.eclipse.cdt.codan.internal.checkers.RedeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.RedefinitionProblem=Error +org.eclipse.cdt.codan.internal.checkers.RedefinitionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.ReturnStyleProblem=-Warning +org.eclipse.cdt.codan.internal.checkers.ReturnStyleProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.ScanfFormatStringSecurityProblem=-Warning +org.eclipse.cdt.codan.internal.checkers.ScanfFormatStringSecurityProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.StatementHasNoEffectProblem=Warning +org.eclipse.cdt.codan.internal.checkers.StatementHasNoEffectProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},macro\=>true,exceptions\=>()} +org.eclipse.cdt.codan.internal.checkers.SuggestedParenthesisProblem=Warning +org.eclipse.cdt.codan.internal.checkers.SuggestedParenthesisProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},paramNot\=>false} +org.eclipse.cdt.codan.internal.checkers.SuspiciousSemicolonProblem=Warning +org.eclipse.cdt.codan.internal.checkers.SuspiciousSemicolonProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},else\=>false,afterelse\=>false} +org.eclipse.cdt.codan.internal.checkers.TypeResolutionProblem=Error +org.eclipse.cdt.codan.internal.checkers.TypeResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem=Warning +org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},macro\=>true} +org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem=Warning +org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},macro\=>true} +org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem=Warning +org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},macro\=>true,exceptions\=>("@(\#)","$Id")} +org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem=Error +org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} diff --git a/assets/shaders/bckg/bckg.frag b/assets/shaders/bckg/bckg_frag.glsl similarity index 100% rename from assets/shaders/bckg/bckg.frag rename to assets/shaders/bckg/bckg_frag.glsl diff --git a/assets/shaders/bckg/bckg.vert b/assets/shaders/bckg/bckg_vert.glsl similarity index 100% rename from assets/shaders/bckg/bckg.vert rename to assets/shaders/bckg/bckg_vert.glsl diff --git a/assets/shaders/singleDiffuseLight/singleDiffuseLight_frag.glsl b/assets/shaders/singleDiffuseLight/singleDiffuseLight_frag.glsl new file mode 100644 index 0000000..04fc4c6 --- /dev/null +++ b/assets/shaders/singleDiffuseLight/singleDiffuseLight_frag.glsl @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2014 Miguel Angel Astor Romero + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef GL_ES +precision mediump float; +#endif + +// Ambient light color. +uniform vec4 u_ambient; + +// Specular light color. +uniform vec4 u_specular; + +// Shininess. +uniform float u_shiny; + +// Fragment position. +varying vec4 v_position; + +// Fragment normal. +varying vec3 v_normal; + +// Fragment color received from the vertex shader. +varying vec4 v_color; + +// Fragment shaded diffuse color. +varying vec4 v_diffuse; + +varying vec3 v_lightVector; +varying vec3 v_eyeVector; +varying vec3 v_reflectedVector; + +void main(){ + // Normalize the input varyings. + vec3 lightVector = normalize(v_lightVector); + vec3 eyeVector = normalize(v_eyeVector); + vec3 reflectedVector = normalize(v_reflectedVector); + + // Specular Term: + vec4 specular = u_specular * pow(max(dot(reflectedVector, eyeVector), 0.0), 0.3 * u_shiny); + + // Aggregate light color. + vec4 lightColor = clamp(vec4(u_ambient.rgb + v_diffuse.rgb + specular.rgb, 1.0), 0.0, 1.0); + + // Final color. + gl_FragColor = clamp(lightColor * v_color, 0.0, 1.0); +} diff --git a/assets/shaders/singleDiffuseLight/singleDiffuseLight_vert.glsl b/assets/shaders/singleDiffuseLight/singleDiffuseLight_vert.glsl new file mode 100644 index 0000000..f125146 --- /dev/null +++ b/assets/shaders/singleDiffuseLight/singleDiffuseLight_vert.glsl @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 Miguel Angel Astor Romero + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Model-view matrix. +uniform mat4 u_projTrans; + +// The world space geometric transformation to apply to this vertex. +uniform mat4 u_geomTrans; + +// Light source position +uniform vec4 u_lightPos; + +// Diffuse light color. +uniform vec4 u_lightDiffuse; + +// Camera world space position. +uniform vec3 u_cameraPos; + +// Vertex position in world coordinates. +attribute vec4 a_position; + +// Vertex normal. +attribute vec4 a_normal; + +// Vertex color. +attribute vec4 a_color; + +// Vertex color to pass to the fragment shader. +varying vec4 v_color; + +// Diffuse shaded color to pass to the fragment shader. +varying vec4 v_diffuse; + +// The vector from the vertex to the light source. +varying vec3 v_lightVector; + +// The vector from the vertex to the camera. +varying vec3 v_eyeVector; + +// The light vector reflected around the vertex normal. +varying vec3 v_reflectedVector; + +void main(){ + // Apply the geometric transformation to the original position of the vertex. + vec4 transformedPosition = u_geomTrans * a_position; + + // Set the varyings. + v_lightVector = normalize(u_lightPos.xyz - transformedPosition.xyz); + v_eyeVector = normalize(u_cameraPos.xyz - transformedPosition.xyz); + v_reflectedVector = normalize(-reflect(v_lightVector, a_normal.xyz)); + v_color = a_color; + + // Diffuse Term. + v_diffuse = u_lightDiffuse * max(dot(a_normal.xyz, v_lightVector), 0.0); + + gl_Position = u_projTrans * transformedPosition; +} diff --git a/jni/Android.mk b/jni/Android.mk index 2e2fa75..8c132e2 100644 --- a/jni/Android.mk +++ b/jni/Android.mk @@ -3,8 +3,9 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) OPENCV_CAMERA_MODULES:=off -OPENCV_LIB_TYPE:=STATIC +OPENCV_LIB_TYPE:=STATIC #SHARED include C:\Users\miguel.astor\Documents\OpenCV-2.4.8-android-sdk\sdk\native\jni\OpenCV.mk +#include C:\NVPACK\OpenCV-2.4.5-Tegra-sdk-r2\sdk\native\jni\OpenCV-tegra3.mk LOCAL_MODULE := cvproc LOCAL_SRC_FILES := cv_proc.cpp marker.cpp @@ -25,4 +26,67 @@ include $(CLEAR_VARS) LOCAL_MODULE := gdx-freetype LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libgdx-freetype.so -include $(PREBUILT_SHARED_LIBRARY) \ No newline at end of file +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) + +LOCAL_MODULE := ocv_tegra_java +LOCAL_SRC_FILES := C:\NVPACK\OpenCV-2.4.5-Tegra-sdk-r2\sdk\native\libs\tegra3\libopencv_java.so + +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) + +LOCAL_MODULE := ocv_tegra_info +LOCAL_SRC_FILES := C:\NVPACK\OpenCV-2.4.5-Tegra-sdk-r2\sdk\native\libs\tegra3\libopencv_info.so + +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) + +LOCAL_MODULE := ocv_tegra_native_camera_220 +LOCAL_SRC_FILES := C:\NVPACK\OpenCV-2.4.5-Tegra-sdk-r2\sdk\native\libs\tegra3\libnative_camera_r2.2.0.so + +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) + +LOCAL_MODULE := ocv_tegra_native_camera_233 +LOCAL_SRC_FILES := C:\NVPACK\OpenCV-2.4.5-Tegra-sdk-r2\sdk\native\libs\tegra3\libnative_camera_r2.3.3.so + +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) + +LOCAL_MODULE := ocv_tegra_native_camera_301 +LOCAL_SRC_FILES := C:\NVPACK\OpenCV-2.4.5-Tegra-sdk-r2\sdk\native\libs\tegra3\libnative_camera_r3.0.1.so + +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) + +LOCAL_MODULE := ocv_tegra_native_camera_400 +LOCAL_SRC_FILES := C:\NVPACK\OpenCV-2.4.5-Tegra-sdk-r2\sdk\native\libs\tegra3\libnative_camera_r4.0.0.so + +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) + +LOCAL_MODULE := ocv_tegra_native_camera_403 +LOCAL_SRC_FILES := C:\NVPACK\OpenCV-2.4.5-Tegra-sdk-r2\sdk\native\libs\tegra3\libnative_camera_r4.0.3.so + +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) + +LOCAL_MODULE := ocv_tegra_native_camera_411 +LOCAL_SRC_FILES := C:\NVPACK\OpenCV-2.4.5-Tegra-sdk-r2\sdk\native\libs\tegra3\libnative_camera_r4.1.1.so + +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) + +LOCAL_MODULE := ocv_tegra_native_camera_420 +LOCAL_SRC_FILES := C:\NVPACK\OpenCV-2.4.5-Tegra-sdk-r2\sdk\native\libs\tegra3\libnative_camera_r4.2.0.so + +include $(PREBUILT_SHARED_LIBRARY) diff --git a/jni/cv_proc.cpp b/jni/cv_proc.cpp index 8afebd8..5ca822e 100644 --- a/jni/cv_proc.cpp +++ b/jni/cv_proc.cpp @@ -16,56 +16,171 @@ #include #include #include +#include #include "marker.hpp" -//#define CAN_LOG - -extern "C"{ -#ifdef CAN_LOG +//#define LOG_ENABLED +#define MAX_MARKERS 5 +#define TRANSLATION_VECTOR_POINTS 3 +#define ROTATION_MATRIX_SIZE 9 +#define POINTS_PER_CALIBRATION_SAMPLE 54 +#define CALIBRATION_SAMPLES 10 +#ifdef LOG_ENABLED #define log(TAG, MSG) (__android_log_write(ANDROID_LOG_DEBUG, TAG, MSG)) +#else +#define log(TAG, MSG) ; +#endif const char * TAG = "CVPROC_NATIVE"; -#else +extern "C"{ -#define log(TAG, MSG) (1 + 1) +/** + * JNI wrapper around the nxtar::getAllMarkers() method. + */ +JNIEXPORT void JNICALL Java_ve_ucv_ciens_ccg_nxtar_MainActivity_getMarkerCodesAndLocations(JNIEnv* env, jobject jobj, jlong addrMatIn, jlong addrMatOut, jintArray codes, jlong camMat, jlong distMat, jfloatArray translations, jfloatArray rotations){ + char codeMsg[128]; + std::vector vCodes; + nxtar::markers_vector vMarkers; + cv::Mat temp; -#endif + // Get the native object addresses. + log(TAG, "getMarkerCodesAndLocations(): Requesting native data."); + cv::Mat& myuv = *(cv::Mat*)addrMatIn; + cv::Mat& mbgr = *(cv::Mat*)addrMatOut; + cv::Mat& mCam = *(cv::Mat*)camMat; + cv::Mat& mDist = *(cv::Mat*)distMat; + jint * _codes = env->GetIntArrayElements(codes, 0); + jfloat * _tr = env->GetFloatArrayElements(translations, 0); + jfloat * _rt = env->GetFloatArrayElements(rotations, 0); - JNIEXPORT void JNICALL Java_ve_ucv_ciens_ccg_nxtar_MainActivity_getMarkerCodesAndLocations( - JNIEnv* env, - jobject jobj, - jlong addrMatIn, - jlong addrMatOut, - jintArray codes - ){ - char codeMsg[128]; - std::vector vCodes; + // Convert the input image to the BGR color space. + log(TAG, "getMarkerCodesAndLocations(): Converting color space before processing."); + cv::cvtColor(myuv, temp, CV_RGB2BGR); - log(TAG, "Requesting native data."); + // Find all markers in the input image. + log(TAG, "getMarkerCodesAndLocations(): Finding markers."); + nxtar::getAllMarkers(vMarkers, temp); + nxtar::estimateMarkerPosition(vMarkers, mCam, mDist); - cv::Mat& myuv = *(cv::Mat*)addrMatIn; - cv::Mat& mbgr = *(cv::Mat*)addrMatOut; - jint * _codes = env->GetIntArrayElements(codes, 0); - - log(TAG, "Converting color space before processing."); - cv::cvtColor(myuv, mbgr, CV_RGB2BGR); - - log(TAG, "Finding markers."); - nxtar::getAllMarkers(vCodes, mbgr); - - log(TAG, "Copying marker codes."); - for(int i = 0; i < vCodes.size() && i < 15; i++){ - _codes[i] = vCodes[i]; - sprintf(codeMsg, "Code [%d] = %d", i, vCodes[i]); - log(TAG, codeMsg); - } - vCodes.clear(); - - log(TAG, "Releasing native data."); - - env->ReleaseIntArrayElements(codes, _codes, 0); + // Copy the marker codes to the output vector. + log(TAG, "getMarkerCodesAndLocations(): Copying marker codes."); + for(size_t i = 0; i < vMarkers.size() && i < MAX_MARKERS; i++){ + _codes[i] = (jint)vMarkers[i].code; } + + // Copy the geometric transformations to the output vectors. + for(int i = 0, p = 0; i < vMarkers.size(); i++, p += 3){ + _tr[p ] = vMarkers[i].translation.at(0); + _tr[p + 1] = vMarkers[i].translation.at(1); + _tr[p + 2] = vMarkers[i].translation.at(2); + } + + for(int k = 0; k < vMarkers.size(); k++){ + for(int row = 0; row < 3; row++){ + for(int col = 0; col < 3; col++){ + _rt[col + (row * 3) + (9 * k)] = vMarkers[k].rotation.at(row, col); + } + } + } + + // Clear marker memory. + vMarkers.clear(); + + // Convert the output image back to the RGB color space. + cv::cvtColor(temp, mbgr, CV_BGR2RGB); + + // Release native data. + log(TAG, "getMarkerCodesAndLocations(): Releasing native data."); + env->ReleaseIntArrayElements(codes, _codes, 0); + env->ReleaseFloatArrayElements(translations, _tr, 0); + env->ReleaseFloatArrayElements(rotations, _rt, 0); } + +/** + * JNI wrapper around the nxtar::findCalibrationPattern() method. + */ +JNIEXPORT jboolean JNICALL Java_ve_ucv_ciens_ccg_nxtar_MainActivity_findCalibrationPattern(JNIEnv* env, jobject jobj, jlong addrMatIn, jlong addrMatOut, jfloatArray points){ + nxtar::points_vector v_points; + bool found; + cv::Mat temp; + + // Get the native object addresses. + log(TAG, "findCalibrationPattern(): Requesting native data."); + cv::Mat& myuv = *(cv::Mat*)addrMatIn; + cv::Mat& mbgr = *(cv::Mat*)addrMatOut; + jfloat * _points = env->GetFloatArrayElements(points, 0); + + // Convert the input image to the BGR color space. + log(TAG, "findCalibrationPattern(): Converting color space before processing."); + cv::cvtColor(myuv, temp, CV_RGB2BGR); + + // Find the calibration points in the input image. + log(TAG, "findCalibrationPattern(): Finding calibration pattern."); + found = nxtar::findCalibrationPattern(v_points, temp); + + // If the points were found then save them to the output array. + if(found){ + log(TAG, "findCalibrationPattern(): Copying calibration points."); + for(size_t i = 0, p = 0; i < v_points.size(); i++, p += 2){ + _points[p ] = (jfloat)v_points[i].x; + _points[p + 1] = (jfloat)v_points[i].y; + } + } + + // Convert the output image back to the RGB color space. + cv::cvtColor(temp, mbgr, CV_BGR2RGB); + + // Release native data. + log(TAG, "findCalibrationPattern(): Releasing native data."); + env->ReleaseFloatArrayElements(points, _points, 0); + + return (jboolean)found; +} + +/** + * JNI wrapper around the nxtar::getCameraParameters() method. + */ +JNIEXPORT jdouble JNICALL Java_ve_ucv_ciens_ccg_nxtar_MainActivity_calibrateCameraParameters(JNIEnv* env, jobject jobj, jlong addrMatIn, jlong addrMatOut, jlong addrMatFrame, jfloatArray points){ + double error; + std::vector imagePoints; + + // Get native object addresses. + log(TAG, "calibrateCameraParameters(): Requesting native data."); + cv::Mat& mIn = *(cv::Mat*)addrMatIn; + cv::Mat& mOut = *(cv::Mat*)addrMatOut; + cv::Mat& mFrame = *(cv::Mat*)addrMatFrame; + jfloat * _points = env->GetFloatArrayElements(points, 0); + + // Prepare the image points data structure. + log(TAG, "calibrateCameraParameters(): Preparing image points."); + for(int i = 0; i < CALIBRATION_SAMPLES; i++){ + nxtar::points_vector tempVector; + for(int j = 0, p = 0; j < POINTS_PER_CALIBRATION_SAMPLE; j++, p += 2){ + tempVector.push_back(cv::Point2f(_points[p], _points[p + 1])); + } + imagePoints.push_back(tempVector); + } + + // Get the camera parameters. + log(TAG, "calibrateCameraParameters(): Getting camera parameters."); + error = nxtar::getCameraParameters(mIn, mOut, imagePoints, mFrame.size()); + + // Clear the image points. + log(TAG, "calibrateCameraParameters(): Clearing memory."); + for(int i = 0; i < imagePoints.size(); i++){ + imagePoints[i].clear(); + } + imagePoints.clear(); + + // Release native memory. + log(TAG, "calibrateCameraParameters(): Releasing native data."); + env->ReleaseFloatArrayElements(points, _points, 0); + + // Return the calibration error as calculated by OpenCV. + return error; +} + +} // extern "C" diff --git a/jni/marker.cpp b/jni/marker.cpp index c4eee4f..ba51285 100644 --- a/jni/marker.cpp +++ b/jni/marker.cpp @@ -16,383 +16,514 @@ #include #include #include +#include +#ifdef DESKTOP #include +#endif #include "marker.hpp" -#define MIN_CONTOUR_LENGTH 0.1 - namespace nxtar{ - static const cv::Scalar COLOR = cv::Scalar(255, 255, 255); +typedef std::vector points_vector_3D; +typedef std::vector > contours_vector; - class Marker; - - typedef std::vector > contours_vector; - typedef std::vector points_vector; - typedef std::vector markers_vector; +/****************************************************************************** + * PRIVATE CONSTANTS * + ******************************************************************************/ - class Marker{ - public: - ~Marker(); - points_vector points; - int code; - }; +/** + * Minimal number of points in a contour. + */ +static const int MIN_POINTS = 40; - float perimeter(points_vector &); - int hammDistMarker(cv::Mat); - cv::Mat rotate(cv::Mat); - void binarize(cv::Mat &, cv::Mat &); - void findContours(cv::Mat &, contours_vector &, int); - void renderContours(contours_vector &, cv::Mat &); - void renderMarkers(markers_vector &, cv::Mat &); - void warpMarker(Marker &, cv::Mat &, cv::Mat &); - int decodeMarker(cv::Mat &); - void fixCorners(cv::Mat &, Marker &); - void isolateMarkers(const contours_vector &, markers_vector &); +/** + * Size of a square cell in the calibration pattern. + */ +static const float SQUARE_SIZE = 1.0f; - void getAllMarkers(std::vector & codes, cv::Mat & img){ - cv::Mat gray, thresh, cont, mark; - contours_vector contours; - markers_vector markers; - markers_vector valid_markers; +/** + * Minimal lenght of a contour to be considered as a marker candidate. + */ +static const float MIN_CONTOUR_LENGTH = 0.1; - codes.clear(); +/** + * Flags for the calibration pattern detecion function. + */ +static const int PATTERN_DETECTION_FLAGS = cv::CALIB_CB_ADAPTIVE_THRESH + cv::CALIB_CB_NORMALIZE_IMAGE + cv::CALIB_CB_FAST_CHECK; - cv::cvtColor(img, gray, CV_BGR2GRAY); - binarize(gray, thresh); - findContours(thresh, contours, 40); - isolateMarkers(contours, markers); +/** + * Color for rendering the marker outlines. + */ +static const cv::Scalar COLOR = cv::Scalar(255, 255, 255); - for(int i = 0; i < markers.size(); i++){ - warpMarker(markers[i], gray, mark); +/** + * Size of the chessboard pattern image (columns, rows). + */ +static const cv::Size CHESSBOARD_PATTERN_SIZE(6, 9); - int code = decodeMarker(mark); +/** + * Termination criteria for OpenCV's iterative algorithms. + */ +static const cv::TermCriteria TERM_CRITERIA = cv::TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1); - if(code != -1){ - markers[i].code = code; - valid_markers.push_back(markers[i]); - } - } +/****************************************************************************** + * PRIVATE FUNCTION PROTOTYPES * + ******************************************************************************/ - for(int i = 0; i < valid_markers.size(); i++) - fixCorners(gray, valid_markers[i]); +float perimeter(points_vector &); - cont = cv::Mat::zeros(img.size(), CV_8UC3); - renderMarkers(valid_markers, cont); +int hammDistMarker(cv::Mat); - img = img + cont; +cv::Mat rotate(cv::Mat); - for(int i = 0; i < valid_markers.size(); i++){ - codes.push_back(valid_markers[i].code); - } +int decodeMarker(cv::Mat &); - markers.clear(); - contours.clear(); - valid_markers.clear(); - } +void renderMarkers(markers_vector &, cv::Mat &); - #ifdef DESKTOP - void getAllMarkers_d(std::vector & codes, cv::Mat & img){ - cv::Mat gray, thresh, cont, mark; - contours_vector contours; - markers_vector markers; - markers_vector valid_markers; - std::ostringstream oss; +void isolateMarkers(const contours_vector &, markers_vector &); - codes.clear(); +void findContours(cv::Mat &, contours_vector &, int); - cv::cvtColor(img, gray, CV_BGR2GRAY); - binarize(gray, thresh); - findContours(thresh, contours, 40); - isolateMarkers(contours, markers); +void warpMarker(Marker &, cv::Mat &, cv::Mat &); - for(int i = 0; i < markers.size(); i++){ - warpMarker(markers[i], gray, mark); +/****************************************************************************** + * PUBLIC API * + ******************************************************************************/ - int code = decodeMarker(mark); +void getAllMarkers(markers_vector & valid_markers, cv::Mat & img){ + cv::Mat gray, thresh, cont, mark; + contours_vector contours; + markers_vector markers; +#ifdef DESKTOP + std::ostringstream oss; +#endif - if(code != -1){ - markers[i].code = code; - valid_markers.push_back(markers[i]); - } - } + valid_markers.clear(); - for(int i = 0; i < valid_markers.size(); i++) - fixCorners(gray, valid_markers[i]); + // Find all marker candidates in the input image. + // 1) First, convert the image to grayscale. + // 2) Then, binarize the grayscale image. + // 3) Finally indentify all 4 sided figures in the binarized image. + cv::cvtColor(img, gray, CV_BGR2GRAY); + cv::adaptiveThreshold(gray, thresh, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY_INV, 7, 7); + findContours(thresh, contours, MIN_POINTS); + isolateMarkers(contours, markers); - cont = cv::Mat::zeros(img.size(), CV_8UC3); - renderMarkers(valid_markers, cont); + // Remove the perspective distortion from the detected marker candidates. + // Then attempt to decode them and push the valid ones into the valid + // markers vector. + for(int i = 0; i < markers.size(); i++){ + warpMarker(markers[i], gray, mark); - img = img + cont; + int code = decodeMarker(mark); - for(int i = 0; i < valid_markers.size(); i++){ - oss << valid_markers[i].code; + if(code != -1){ + markers[i].code = code; + valid_markers.push_back(markers[i]); + } + } - cv::putText(mark, oss.str(), cv::Point(5, 250), cv::FONT_HERSHEY_PLAIN, 2, cv::Scalar::all(128), 3, 8); + for(int i = 0; i < valid_markers.size(); i++){ +#ifdef DESKTOP + // Render the detected valid markers with their codes for debbuging + // purposes. + oss << valid_markers[i].code; - oss.str(""); - oss.clear(); + cv::putText(mark, oss.str(), cv::Point(5, 250), cv::FONT_HERSHEY_PLAIN, 2, cv::Scalar::all(128), 3, 8); - oss << "Marker[" << i << "]"; + oss.str(""); + oss.clear(); - cv::imshow(oss.str(), mark); + oss << "Marker[" << i << "]"; - oss.str(""); - oss.clear(); + cv::imshow(oss.str(), mark); - codes.push_back(valid_markers[i].code); - } + oss.str(""); + oss.clear(); +#endif + // Fix the detected corners to better approximate the markers. And + // push their codes to the output vector. + cv::cornerSubPix(gray, valid_markers[i].points, cvSize(10, 10), cvSize(-1, -1), TERM_CRITERIA); + } - markers.clear(); - contours.clear(); - valid_markers.clear(); - } - #endif - - void binarize(cv::Mat & src, cv::Mat & dst){ - cv::adaptiveThreshold(src, dst, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY_INV, 7, 7); - } - - void findContours(cv::Mat & img, contours_vector & v, int minP){ - std::vector > c; - cv::findContours(img, c, CV_RETR_LIST, CV_CHAIN_APPROX_NONE); - - v.clear(); - for(size_t i = 0; i < c.size(); i++){ - if(c[i].size() > minP){ - v.push_back(c[i]); - } - } - } - - void renderContours(contours_vector & v, cv::Mat & dst){ - cv::drawContours(dst, v, -1, COLOR, 1, CV_AA); - } - - void renderMarkers(markers_vector & v, cv::Mat & dst){ - contours_vector cv; - - for(size_t i = 0; i < v.size(); i++){ - std::vector pv; - for(size_t j = 0; j < v[i].points.size(); ++j) - pv.push_back(cv::Point2f(v[i].points[j].x, v[i].points[j].y)); - cv.push_back(pv); - } - - cv::drawContours(dst, cv, -1, COLOR, 1, CV_AA); - } - - void isolateMarkers(const contours_vector & vc, markers_vector & vm){ - std::vector appCurve; - markers_vector posMarkers; - - for(size_t i = 0; i < vc.size(); ++i){ - double eps = vc[i].size() * 0.05; - cv::approxPolyDP(vc[i], appCurve, eps, true); - - if(appCurve.size() != 4 || !cv::isContourConvex(appCurve)) continue; - - float minD = std::numeric_limits::max(); - - for(int i = 0; i < 4; i++){ - cv::Point side = appCurve[i] - appCurve[(i + 1) % 4]; - float sqSideLen = side.dot(side); - minD = std::min(minD, sqSideLen); - } - - if(minD < MIN_CONTOUR_LENGTH) continue; - - Marker m; - - for(int i = 0; i < 4; i++) - m.points.push_back(cv::Point2f(appCurve[i].x, appCurve[i].y)); - - cv::Point v1 = m.points[1] - m.points[0]; - cv::Point v2 = m.points[2] - m.points[0]; - - double o = (v1.x * v2.y) - (v1.y * v2.x); - if(o < 0.0) std::swap(m.points[1], m.points[3]); - - posMarkers.push_back(m); - } - - std::vector > tooNear; - for(size_t i = 0; i < posMarkers.size(); ++i){ - const Marker & m1 = posMarkers[i]; - - for(size_t j = i + 1; j < posMarkers.size(); j++){ - const Marker & m2 = posMarkers[j]; - - float dSq = 0.0f; - - for(int c = 0; c < 4; c++){ - cv::Point v = m1.points[c] - m2.points[c]; - dSq += v.dot(v); - } - - dSq /= 4.0f; - - if(dSq < 100) tooNear.push_back(std::pair(i, j)); - } - } - - std::vector remMask(posMarkers.size(), false); - - for(size_t i = 0; i < tooNear.size(); ++i){ - float p1 = perimeter(posMarkers[tooNear[i].first].points); - float p2 = perimeter(posMarkers[tooNear[i].second].points); - - size_t remInd; - if(p1 > p2) remInd = tooNear[i].second; - else remInd = tooNear[i].first; - - remMask[remInd] = true; - } - - vm.clear(); - for(size_t i = 0; i < posMarkers.size(); ++i){ - if(remMask[i]) vm.push_back(posMarkers[i]); - } - } - - void warpMarker(Marker & m, cv::Mat & in, cv::Mat & out){ - cv::Mat bin; - cv::Size markerSize(350, 350); - points_vector v; - v.push_back(cv::Point2f(0,0)); - v.push_back(cv::Point2f(markerSize.width-1,0)); - v.push_back(cv::Point2f(markerSize.width-1,markerSize.height-1)); - v.push_back(cv::Point2f(0,markerSize.height-1)); - - cv::Mat M = cv::getPerspectiveTransform(m.points, v); - cv::warpPerspective(in, bin, M, markerSize); - - cv::threshold(bin, out, 128, 255, cv::THRESH_BINARY | cv::THRESH_OTSU); - } - - int hammDistMarker(cv::Mat bits){ - int ids[4][5] = { - {1,0,0,0,0}, - {1,0,1,1,1}, - {0,1,0,0,1}, - {0,1,1,1,0} - }; - - int dist = 0; - - for (int y = 0; y < 5; y++){ - int minSum = 1e5; - - for (int p = 0; p < 4; p++){ - int sum = 0; - - for (int x = 0; x < 5; x++){ - sum += bits.at(y, x) == ids[p][x] ? 0 : 1; - } - - if(minSum > sum) - minSum = sum; - } - - dist += minSum; - } - - return dist; - } - cv::Mat rotate(cv::Mat in){ - cv::Mat out; - in.copyTo(out); - for (int i=0;i(i,j)=in.at(in.cols-j-1,i); - } - } - - return out; - } - - int decodeMarker(cv::Mat & marker){ - bool found = false; - int code = 0; - cv::Mat bits; - - for(int y = 0; y < 7; y++){ - int inc = (y == 0 || y == 6) ? 1 : 6; - - for(int x = 0; x < 7; x += inc){ - int cX = x * 50; - int cY = y * 50; - - cv::Mat cell = marker(cv::Rect(cX, cY, 50, 50)); - - int nZ = cv::countNonZero(cell); - - // Not a valid marker. - if(nZ > (50 * 50) / 2) return -1; - } - } - - bits = cv::Mat::zeros(5, 5, CV_8UC1); - - - for(int y = 0; y < 5; y++){ - for(int x = 0; x < 5; x++){ - int cX = (x + 1) * 50; - int cY = (y + 1) * 50; - - cv::Mat cell = marker(cv::Rect(cX, cY, 50, 50)); - - int nZ = cv::countNonZero(cell); - - if(nZ > (50 * 50) / 2) bits.at(y, x) = 1; - } - } - - if(hammDistMarker(bits) != 0){ - for(int r = 1; r < 4; r++){ - bits = rotate(bits); - if(hammDistMarker(bits) != 0) continue; - else{ found = true; break;} - } - }else found = true; - - - if(found){ - for(int y = 0; y < 5; y++){ - code <<= 1; - if(bits.at(y, 1)) - code |= 1; - - code <<= 1; - if(bits.at(y, 3)) - code |= 1; - } - - - return code; - }else - return -1; - } - - void fixCorners(cv::Mat & img, Marker & m){ - cv::cornerSubPix(img, m.points, cvSize(10, 10), cvSize(-1, -1), cvTermCriteria(CV_TERMCRIT_ITER, 30, 0.1)); - } - - float perimeter(points_vector & p){ - float per = 0.0f, dx, dy; - - for(size_t i; i < p.size(); ++i){ - dx = p[i].x - p[(i + 1) % p.size()].x; - dy = p[i].y - p[(i + 1) % p.size()].y; - per += sqrt((dx * dx) + (dy * dy)); - } - - return per; - } - - Marker::~Marker(){ - points.clear(); - } + // Render the detected markers on top of the input image. + cont = cv::Mat::zeros(img.size(), CV_8UC3); + renderMarkers(valid_markers, cont); + img = img + cont; + // Clear the local vectors. + markers.clear(); + contours.clear(); } + +bool findCalibrationPattern(points_vector & corners, cv::Mat & img){ + bool patternfound; + cv::Mat gray; + + // Convert the input image to grayscale and attempt to find the + // calibration pattern. + cv::cvtColor(img, gray, CV_BGR2GRAY); + patternfound = cv::findChessboardCorners(gray, CHESSBOARD_PATTERN_SIZE, corners, PATTERN_DETECTION_FLAGS); + + // If the pattern was found then fix the detected points a bit. + if(patternfound) + cv::cornerSubPix(gray, corners, cv::Size(11, 11), cv::Size(-1, -1), TERM_CRITERIA); + + // Render the detected pattern. + cv::drawChessboardCorners(img, CHESSBOARD_PATTERN_SIZE, cv::Mat(corners), patternfound); + + return patternfound; +} + +double getCameraParameters(cv::Mat & camera_matrix, cv::Mat & dist_coeffs, std::vector & image_points, cv::Size image_size){ + std::vector rvecs, tvecs; + std::vector object_points; + points_vector_3D corner_points; + + // Build the reference object points vector. + for(int i = 0; i < CHESSBOARD_PATTERN_SIZE.height; i++){ + for(int j = 0; j < CHESSBOARD_PATTERN_SIZE.width; j++){ + corner_points.push_back(cv::Point3f(float( j * SQUARE_SIZE ), float( i * SQUARE_SIZE ), 0)); + } + } + object_points.push_back(corner_points); + object_points.resize(image_points.size(), object_points[0]); + + // Build a camera matrix. + camera_matrix = cv::Mat::eye(3, 3, CV_64F); + + // Build the distortion coefficients matrix. + dist_coeffs = cv::Mat::zeros(8, 1, CV_64F); + + // Calibrate and return the reprojection error. + return cv::calibrateCamera(object_points, image_points, image_size, camera_matrix, dist_coeffs, rvecs, tvecs, 0, TERM_CRITERIA); +} + +void estimateMarkerPosition(markers_vector & markers, cv::Mat & camMatrix, cv::Mat & distCoeff){ + cv::Mat rVec, rAux, tAux; + cv::Mat_ tVec, rotation(3,3); + points_vector_3D objectPoints; + + // Assemble a unitary CCW oriented reference polygon. + objectPoints.push_back(cv::Point3f(-0.5f, -0.5f, 0.0f)); + objectPoints.push_back(cv::Point3f(-0.5f, 0.5f, 0.0f)); + objectPoints.push_back(cv::Point3f( 0.5f, 0.5f, 0.0f)); + objectPoints.push_back(cv::Point3f( 0.5f, -0.5f, 0.0f)); + + for(size_t i = 0; i < markers.size(); i++){ + // Obtain the translation and rotation vectors. + cv::solvePnP(objectPoints, markers[i].points, camMatrix, distCoeff, rAux, tAux); + + // Convert the obtained vectors to float. + rAux.convertTo(rVec, CV_32F); + tAux.convertTo(tVec, CV_32F); + + // Convert the rotation vector to a rotation matrix. + cv::Rodrigues(rVec, rotation); + + // Make the rotation and translation relative to the "camera" and save + // the results to the output marker. + markers[i].rotation = cv::Mat(rotation.t()); + markers[i].translation = cv::Mat(-tVec); + } +} + +/****************************************************************************** + * PRIVATE HELPER FUNCTIONS * + ******************************************************************************/ + +/** + * Find all contours in the input image and save them to the output + * vector. + */ +void findContours(cv::Mat & img, contours_vector & v, int minP){ + contours_vector c; + + // A contour is discarded if it possess less than the specified + // minimum number of points. + cv::findContours(img, c, CV_RETR_LIST, CV_CHAIN_APPROX_NONE); + + v.clear(); + for(size_t i = 0; i < c.size(); i++){ + if(c[i].size() > minP){ + v.push_back(c[i]); + } + } +} + +/** + * Render the input marker vector onto the output image. + */ +void renderMarkers(markers_vector & v, cv::Mat & dst){ + contours_vector cv; + + // Extract the points that form every marker into a contours vector. + for(size_t i = 0; i < v.size(); i++){ + std::vector pv; + for(size_t j = 0; j < v[i].points.size(); ++j) + pv.push_back(cv::Point2f(v[i].points[j].x, v[i].points[j].y)); + cv.push_back(pv); + } + + // Render. + cv::drawContours(dst, cv, -1, COLOR, 1, CV_AA); +} + +/** + * Identify and return all marker candidates. + */ +void isolateMarkers(const contours_vector & vc, markers_vector & vm){ + std::vector appCurve; + markers_vector posMarkers; + + // For every detected contour. + for(size_t i = 0; i < vc.size(); ++i){ + double eps = vc[i].size() * 0.05; + + // Approximate the contour with a polygon. + cv::approxPolyDP(vc[i], appCurve, eps, true); + + // If the polygon is not a cuadrilateral then this is not a marker + // candidate. + if(appCurve.size() != 4 || !cv::isContourConvex(appCurve)) continue; + + // Calculate the lenght of the perimeter of this candidate. If it + // is too short then discard it. + float minD = std::numeric_limits::max(); + + for(int i = 0; i < 4; i++){ + cv::Point side = appCurve[i] - appCurve[(i + 1) % 4]; + float sqSideLen = side.dot(side); + minD = std::min(minD, sqSideLen); + } + + if(minD < MIN_CONTOUR_LENGTH) continue; + + // Save the marker and order it's points counter-clockwise. + Marker m; + + for(int i = 0; i < 4; i++) + m.points.push_back(cv::Point2f(appCurve[i].x, appCurve[i].y)); + + cv::Point v1 = m.points[1] - m.points[0]; + cv::Point v2 = m.points[2] - m.points[0]; + + double o = (v1.x * v2.y) - (v1.y * v2.x); + if(o < 0.0) std::swap(m.points[1], m.points[3]); + + posMarkers.push_back(m); + } + + // Identify contours that are to close to each other to eliminate + // possible duplicates. + std::vector > tooNear; + for(size_t i = 0; i < posMarkers.size(); ++i){ + const Marker & m1 = posMarkers[i]; + + for(size_t j = i + 1; j < posMarkers.size(); j++){ + const Marker & m2 = posMarkers[j]; + + float dSq = 0.0f; + + for(int c = 0; c < 4; c++){ + cv::Point v = m1.points[c] - m2.points[c]; + dSq += v.dot(v); + } + + dSq /= 4.0f; + + if(dSq < 100) tooNear.push_back(std::pair(i, j)); + } + } + + // Mark one of every pair of duplicates to be discarded. + std::vector remMask(posMarkers.size(), false); + for(size_t i = 0; i < tooNear.size(); ++i){ + float p1 = perimeter(posMarkers[tooNear[i].first].points); + float p2 = perimeter(posMarkers[tooNear[i].second].points); + + size_t remInd; + if(p1 > p2) remInd = tooNear[i].second; + else remInd = tooNear[i].first; + + remMask[remInd] = true; + } + + // Save the candidates that survided the duplicates test. + vm.clear(); + for(size_t i = 0; i < posMarkers.size(); ++i){ + if(!remMask[i]) vm.push_back(posMarkers[i]); + } +} + +/** + * Warp a marker image to remove it's perspective distortion. + */ +void warpMarker(Marker & m, cv::Mat & in, cv::Mat & out){ + cv::Mat bin; + cv::Size markerSize(350, 350); + points_vector v; + + // Assemble a unitary reference polygon. + v.push_back(cv::Point2f(0,0)); + v.push_back(cv::Point2f(markerSize.width-1,0)); + v.push_back(cv::Point2f(markerSize.width-1,markerSize.height-1)); + v.push_back(cv::Point2f(0,markerSize.height-1)); + + // Warp the input image's perspective to transform it into the reference + // polygon's perspective. + cv::Mat M = cv::getPerspectiveTransform(m.points, v); + cv::warpPerspective(in, bin, M, markerSize); + + // Binarize the warped image into the output image. + cv::threshold(bin, out, 128, 255, cv::THRESH_BINARY | cv::THRESH_OTSU); +} + +/** + * Calculate the hamming distance of a 5x5 bit matrix. + * Function by Daniel Lelis Baggio for "Mastering OpenCV with Practical Computer Vision Projects". + */ +int hammDistMarker(cv::Mat bits){ + int ids[4][5] = { + {1,0,0,0,0}, + {1,0,1,1,1}, + {0,1,0,0,1}, + {0,1,1,1,0} + }; + + int dist = 0; + + for (int y = 0; y < 5; y++){ + int minSum = 1e5; + + for (int p = 0; p < 4; p++){ + int sum = 0; + + for (int x = 0; x < 5; x++){ + sum += bits.at(y, x) == ids[p][x] ? 0 : 1; + } + + if(minSum > sum) + minSum = sum; + } + + dist += minSum; + } + + return dist; +} + +/** + * Rotate a matrix by 90 degrees clockwise. + */ +cv::Mat rotate(cv::Mat in){ + cv::Mat out; + + in.copyTo(out); + for (int i = 0; i < in.rows; i++){ + for (int j = 0; j < in.cols; j++){ + out.at(i, j) = in.at(in.cols-j - 1, i); + } + } + + return out; +} + +/** + * Decode a marker image and return it's code. Returns -1 if the image is + * not a valid marker. + */ +int decodeMarker(cv::Mat & marker){ + bool found = false; + int code = 0; + cv::Mat bits; + + // Verify that the outer rim of marker cells are all black. + for(int y = 0; y < 7; y++){ + int inc = (y == 0 || y == 6) ? 1 : 6; + + for(int x = 0; x < 7; x += inc){ + int cX = x * 50; + int cY = y * 50; + cv::Mat cell = marker(cv::Rect(cX, cY, 50, 50)); + + int nZ = cv::countNonZero(cell); + + // If one of the rim cells is 50% white or more then this + // is not a valid marker. + if(nZ > (50 * 50) / 2) return -1; + } + } + + // Create a 5x5 matrix to hold a simplified representation of this + // marker. + bits = cv::Mat::zeros(5, 5, CV_8UC1); + + // For every cell in the marker flip it's corresponding 'bit' in the + // bit matrix if it is at least 50 % white. + for(int y = 0; y < 5; y++){ + for(int x = 0; x < 5; x++){ + int cX = (x + 1) * 50; + int cY = (y + 1) * 50; + cv::Mat cell = marker(cv::Rect(cX, cY, 50, 50)); + int nZ = cv::countNonZero(cell); + + if(nZ > (50 * 50) / 2) bits.at(y, x) = 1; + } + } + + // Calcultate the hamming distance of the bit matrix and each of it's + // 90 degree rotations to determine if this marker has a valid code. + if(hammDistMarker(bits) != 0){ + for(int r = 1; r < 4; r++){ + bits = rotate(bits); + if(hammDistMarker(bits) != 0) continue; + else{ found = true; break;} + } + }else found = true; + + // If the code is valid then decode it to an int and return it. + if(found){ + for(int y = 0; y < 5; y++){ + code <<= 1; + if(bits.at(y, 1)) + code |= 1; + + code <<= 1; + if(bits.at(y, 3)) + code |= 1; + } + + + return code; + }else + return -1; +} + +/** + * Calculate the perimeter of a polygon defined as a vector of points. + */ +float perimeter(points_vector & p){ + float per = 0.0f, dx, dy; + + for(size_t i; i < p.size(); ++i){ + dx = p[i].x - p[(i + 1) % p.size()].x; + dy = p[i].y - p[(i + 1) % p.size()].y; + per += sqrt((dx * dx) + (dy * dy)); + } + + return per; +} + +/****************************************************************************** + * CLASS METHODS * + ******************************************************************************/ + +/** + * Clear the points vector associated with this marker. + */ +Marker::~Marker(){ + points.clear(); +} +} // namespace nxtar diff --git a/jni/marker.hpp b/jni/marker.hpp index 1164729..2bae666 100644 --- a/jni/marker.hpp +++ b/jni/marker.hpp @@ -17,13 +17,58 @@ #define MARKER_HPP #include + #include namespace nxtar{ - void getAllMarkers(std::vector &, cv::Mat &); -#ifdef DESKTOP - void getAllMarkers_d(std::vector &, cv::Mat &); -#endif -} -#endif +class Marker; + +typedef std::vector points_vector; +typedef std::vector markers_vector; + +/** + * A class representing a marker with the geometric transformations needed to + * render something on top of it. + */ +class Marker{ +public: + ~Marker(); + + int code; + points_vector points; + cv::Mat translation; + cv::Mat rotation; +}; + +/** + * Detect all 5x5 markers in the input image and return their codes in the + * output vector. + */ +void getAllMarkers(markers_vector &, cv::Mat &); + +/** + * Find a chessboard calibration pattern in the input image. Returns true + * if the pattern was found, false otherwise. The calibration points + * detected on the image are saved in the output vector. + */ +bool findCalibrationPattern(points_vector &, cv::Mat &); + +/** + * Sets the camera matrix and the distortion coefficients for the camera + * that captured the input image points into the output matrices. Returns + * the reprojection error as returned by cv::calibrateCamera. + */ +double getCameraParameters(cv::Mat &, cv::Mat &, std::vector &, cv::Size); + +/** + * Obtains the necesary geometric transformations necessary to move a reference + * unitary polygon to the position and rotation of the markers passed as input. + * The obtained transformations are given relative to a camera centered in the + * origin and are saved inside the input markers. + */ +void estimateMarkerPosition(markers_vector &, cv::Mat &, cv::Mat &); + +} // namespace nxtar + +#endif // MARKER_HPP diff --git a/src/ve/ucv/ciens/ccg/nxtar/MainActivity.java b/src/ve/ucv/ciens/ccg/nxtar/MainActivity.java index dfccb39..ee04529 100644 --- a/src/ve/ucv/ciens/ccg/nxtar/MainActivity.java +++ b/src/ve/ucv/ciens/ccg/nxtar/MainActivity.java @@ -24,9 +24,9 @@ import org.opencv.android.Utils; import org.opencv.core.Mat; import org.opencv.imgproc.Imgproc; -import ve.ucv.ciens.ccg.nxtar.interfaces.CVProcessor; -import ve.ucv.ciens.ccg.nxtar.interfaces.MulticastEnabler; -import ve.ucv.ciens.ccg.nxtar.interfaces.Toaster; +import ve.ucv.ciens.ccg.nxtar.interfaces.AndroidFunctionalityWrapper; +import ve.ucv.ciens.ccg.nxtar.interfaces.ImageProcessor; +import ve.ucv.ciens.ccg.nxtar.utils.ProjectConstants; import android.content.Context; import android.content.pm.ActivityInfo; import android.graphics.Bitmap; @@ -42,73 +42,213 @@ import com.badlogic.gdx.Gdx; import com.badlogic.gdx.backends.android.AndroidApplication; import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration; import com.badlogic.gdx.controllers.mappings.Ouya; +import com.badlogic.gdx.math.Matrix3; +import com.badlogic.gdx.math.Vector3; -public class MainActivity extends AndroidApplication implements Toaster, MulticastEnabler, CVProcessor{ +/** + *

The main activity of the application.

+ * + *

Provides operating system services to the LibGDX platform + * independant code, and handles OpenCV initialization and api calls.

+ */ +public class MainActivity extends AndroidApplication implements AndroidFunctionalityWrapper, ImageProcessor{ + /** + * Tag used for logging. + */ private static final String TAG = "NXTAR_ANDROID_MAIN"; + + /** + * Class name used for logging. + */ private static final String CLASS_NAME = MainActivity.class.getSimpleName(); + /** + * Output stream used to codify images as JPEG using Android's Bitmap class. + */ + private static final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + /** + * Indicates if OpenCV was initialized sucessfully. + */ + private static boolean ocvOn = false; + + /** + * Intrinsic camera matrix. + */ + private static Mat cameraMatrix; + + /** + * Distortion coeffitients matrix. + */ + private static Mat distortionCoeffs; + + /** + * Used to set and release multicast locks. + */ private WifiManager wifiManager; + + /** + * Used to maintain the multicast lock during the service discovery procedure. + */ private MulticastLock multicastLock; + + /** + * Handler used for requesting toast messages from the core LibGDX code. + */ private Handler uiHandler; + + /** + * User interface context used to show the toast messages. + */ private Context uiContext; - private boolean ocvOn; + + /** + * OpenCV asynchronous initializer callback for mobile devices. + */ private BaseLoaderCallback loaderCallback; - private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - /*static{ - if (!OpenCVLoader.initDebug()){ - Gdx.app.exit(); - } - System.loadLibrary("cvproc"); - }*/ + /** + * Indicates if the current video streaming camera has been calibrated. + */ + private boolean cameraCalibrated; - public native void getMarkerCodesAndLocations(long inMat, long outMat, int[] codes); + /** + *

Wrapper for the getAllMarkers native function.

+ * + * @param inMat INPUT. The image to analize. + * @param outMat OUTPUT. The image with the markers highlighted. + * @param codes OUTPUT. The codes for each marker detected. Must be {@link ProjectConstants.MAXIMUM_NUMBER_OF_MARKERS} elements long. + * @param camMat INPUT. The intrinsic camera matrix. + * @param distMat INPUT. The distortion coefficients of the camera. + * @param translations OUTPUT. The markers pose translations. Must be {@link ProjectConstants.MAXIMUM_NUMBER_OF_MARKERS} * 3 elements long. + * @param rotations OUTPUT. The markers pose rotations. Must be {@link ProjectConstants.MAXIMUM_NUMBER_OF_MARKERS} * 9 elements long. + */ + private native void getMarkerCodesAndLocations(long inMat, long outMat, int[] codes, long camMat, long distMat, float[] translations, float[] rotations); + /** + *

Wrapper for the findCalibrationPattern native function.

+ * + * @param inMat INPUT. The image to analize. + * @param outMat OUTPUT. The image with the calibration pattern highlighted. + * @param points OUTPUT. The spatial location of the calibration points if found. + * @return True if the calibration pattern was found. False otherwise. + */ + private native boolean findCalibrationPattern(long inMat, long outMat, float[] points); + + /** + *

Wrapper around the getCameraParameters native function.

+ * + * @param camMat OUTPUT. The intrinsic camera matrix. + * @param distMat OUTPUT. The distortion coeffitients matrix. + * @param frame INPUT. A sample input image from the camera to calibrate. + * @param calibrationPoints INPUT. The calibration points of all samples. + * @return The calibration error as returned by OpenCV. + */ + private native double calibrateCameraParameters(long camMat, long distMat, long frame, float[] calibrationPoints); + + /** + *

Static block. Tries to load OpenCV and the native method implementations + * statically if running on an OUYA device.

+ */ + static{ + if(Ouya.runningOnOuya){ + if(!OpenCVLoader.initDebug()) + ocvOn = false; + + try{ + System.loadLibrary("cvproc"); + ocvOn = true; + }catch(UnsatisfiedLinkError u){ + ocvOn = false; + } + } + } + + /** + *

Initializes this activity

+ * + *

This method handles the initialization of LibGDX and OpenCV. OpenCV is + * loaded the asynchronous method if the devices is not an OUYA console.

+ * + * @param savedInstanceState The application state if it was saved in a previous run. + */ @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); - ocvOn = false; + cameraCalibrated = false; + // Set screen orientation. Portrait on mobile devices, landscape on OUYA. if(!Ouya.runningOnOuya){ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); }else{ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } + // Set up the Android related variables. uiHandler = new Handler(); uiContext = this; wifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE); + // Attempt to initialize OpenCV. + if(!Ouya.runningOnOuya){ + // If running on a moble device, use the asynchronous method aided + // by the OpenCV Manager app. + loaderCallback = new BaseLoaderCallback(this){ + @Override + public void onManagerConnected(int status){ + switch(status){ + case LoaderCallbackInterface.SUCCESS: + // If successfully initialized then load the native method implementations and + // initialize the static matrices. + System.loadLibrary("cvproc"); + ocvOn = true; + cameraMatrix = new Mat(); + distortionCoeffs = new Mat(); + break; + + default: + Toast.makeText(uiContext, R.string.ocv_failed, Toast.LENGTH_LONG).show(); + ocvOn = false; + break; + } + } + }; + + // Launch the asynchronous initializer. + OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_7, this, loaderCallback); + + }else{ + // If running on an OUYA device. + if(ocvOn){ + // If OpenCV loaded successfully then initialize the native matrices. + cameraMatrix = new Mat(); + distortionCoeffs = new Mat(); + }else{ + Toast.makeText(uiContext, R.string.ocv_failed, Toast.LENGTH_LONG).show(); + } + } + + // Configure LibGDX. AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration(); cfg.useGL20 = true; cfg.useAccelerometer = false; cfg.useCompass = false; cfg.useWakelock = true; - loaderCallback = new BaseLoaderCallback(this){ - @Override - public void onManagerConnected(int status){ - switch(status){ - case LoaderCallbackInterface.SUCCESS: - System.loadLibrary("cvproc"); - ocvOn = true; - break; - default: - Toast.makeText(uiContext, R.string.ocv_failed, Toast.LENGTH_LONG).show(); - Gdx.app.exit(); - break; - } - } - }; - - OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_7, this, loaderCallback); + // Launch the LibGDX core game class. initialize(new NxtARCore(this), cfg); } - //////////////////////////////// - // Toaster interface methods. // - //////////////////////////////// + //////////////////////////////////////////////// + // OSFunctionalityProvider interface methods. // + //////////////////////////////////////////////// + + /** + *

Shows a short message on screen using Android's toast mechanism.

+ * + * @param msg The message to show. + */ @Override public void showShortToast(final String msg){ uiHandler.post(new Runnable(){ @@ -119,6 +259,11 @@ public class MainActivity extends AndroidApplication implements Toaster, Multica }); } + /** + *

Shows a long message on screen using Android's toast mechanism.

+ * + * @param msg The message to show. + */ @Override public void showLongToast(final String msg){ uiHandler.post(new Runnable(){ @@ -129,9 +274,9 @@ public class MainActivity extends AndroidApplication implements Toaster, Multica }); } - ///////////////////////////////////////// - // MulticastEnabler interface methods. // - ///////////////////////////////////////// + /** + *

Enable the transmision and reception of multicast network messages.

+ */ @Override public void enableMulticast(){ Gdx.app.log(TAG, CLASS_NAME + ".enableMulticast() :: Requesting multicast lock."); @@ -140,6 +285,9 @@ public class MainActivity extends AndroidApplication implements Toaster, Multica multicastLock.acquire(); } + /** + *

Disables the transmision and reception of multicast network messages.

+ */ @Override public void disableMulticast(){ Gdx.app.log(TAG, CLASS_NAME + ".disableMulticast() :: Releasing multicast lock."); @@ -149,40 +297,205 @@ public class MainActivity extends AndroidApplication implements Toaster, Multica } } + //////////////////////////////////// + // CVProcessor interface methods. // + //////////////////////////////////// + @Override - public CVData processFrame(byte[] frame, int w, int h) { + public MarkerData findMarkersInFrame(byte[] frame){ if(ocvOn){ - int codes[] = new int[15]; + if(cameraCalibrated){ + int[] codes = new int[ProjectConstants.MAXIMUM_NUMBER_OF_MARKERS]; + float[] translations = new float[ProjectConstants.MAXIMUM_NUMBER_OF_MARKERS * 3]; + float[] rotations = new float[ProjectConstants.MAXIMUM_NUMBER_OF_MARKERS * 9]; + MarkerData data; + Bitmap tFrame, mFrame; + Mat inImg = new Mat(); + Mat outImg = new Mat(); + + // Fill the codes array with -1 to indicate markers that were not found; + for(int i : codes) + codes[i] = -1; + + // Decode the input image and convert it to an OpenCV matrix. + tFrame = BitmapFactory.decodeByteArray(frame, 0, frame.length); + Utils.bitmapToMat(tFrame, inImg); + + // Find the markers in the input image. + getMarkerCodesAndLocations(inImg.getNativeObjAddr(), outImg.getNativeObjAddr(), codes, cameraMatrix.getNativeObjAddr(), distortionCoeffs.getNativeObjAddr(), translations, rotations); + + // Encode the output image as a JPEG image. + mFrame = Bitmap.createBitmap(outImg.cols(), outImg.rows(), Bitmap.Config.RGB_565); + Utils.matToBitmap(outImg, mFrame); + mFrame.compress(CompressFormat.JPEG, 100, outputStream); + + // Create and fill the output data structure. + data = new MarkerData(); + data.outFrame = outputStream.toByteArray(); + data.markerCodes = codes; + data.rotationMatrices = new Matrix3[ProjectConstants.MAXIMUM_NUMBER_OF_MARKERS]; + data.translationVectors = new Vector3[ProjectConstants.MAXIMUM_NUMBER_OF_MARKERS]; + + for(int i = 0, p = 0; i < ProjectConstants.MAXIMUM_NUMBER_OF_MARKERS; i++, p += 3){ + data.translationVectors[i] = new Vector3(translations[p], translations[p + 1], translations[p + 2]); + } + + for(int k = 0; k < ProjectConstants.MAXIMUM_NUMBER_OF_MARKERS; k++){ + data.rotationMatrices[k] = new Matrix3(); + for(int row = 0; row < 3; row++){ + for(int col = 0; col < 3; col++){ + data.rotationMatrices[k].val[col + (row * 3)] = rotations[col + (row * 3) + (9 * k)]; + } + } + } + + // Clean up memory. + tFrame.recycle(); + mFrame.recycle(); + outputStream.reset(); + + return data; + }else{ + Gdx.app.debug(TAG, CLASS_NAME + ".findMarkersInFrame(): The camera has not been calibrated."); + return null; + } + }else{ + Gdx.app.debug(TAG, CLASS_NAME + ".findMarkersInFrame(): OpenCV is not ready or failed to load."); + return null; + } + } + + @Override + public CalibrationData findCalibrationPattern(byte[] frame){ + if(ocvOn){ + boolean found; + float points[] = new float[ProjectConstants.CALIBRATION_PATTERN_POINTS * 2]; Bitmap tFrame, mFrame; + Mat inImg = new Mat(), outImg = new Mat(); + CalibrationData data = new CalibrationData(); + // Decode the input frame and convert it to an OpenCV Matrix. tFrame = BitmapFactory.decodeByteArray(frame, 0, frame.length); - - Mat inImg = new Mat(); - Mat outImg = new Mat(); Utils.bitmapToMat(tFrame, inImg); - getMarkerCodesAndLocations(inImg.getNativeObjAddr(), outImg.getNativeObjAddr(), codes); + // Attempt to find the calibration pattern in the input frame. + found = findCalibrationPattern(inImg.getNativeObjAddr(), outImg.getNativeObjAddr(), points); - Mat temp = new Mat(); - Imgproc.cvtColor(outImg, temp, Imgproc.COLOR_BGR2RGB); - - mFrame = Bitmap.createBitmap(temp.cols(), temp.rows(), Bitmap.Config.RGB_565); - Utils.matToBitmap(temp, mFrame); + // Encode the output image as a JPEG image. + mFrame = Bitmap.createBitmap(outImg.cols(), outImg.rows(), Bitmap.Config.RGB_565); + Utils.matToBitmap(outImg, mFrame); mFrame.compress(CompressFormat.JPEG, 100, outputStream); - CVData data = new CVData(); + // Prepare the output data structure. data.outFrame = outputStream.toByteArray(); - data.markerCodes = codes; + data.calibrationPoints = found ? points : null; + // Clean up memory. tFrame.recycle(); mFrame.recycle(); outputStream.reset(); return data; - }else{ - Gdx.app.debug(TAG, CLASS_NAME + ".processFrame(): OpenCV is not ready or failed to load."); + }else{ + Gdx.app.debug(TAG, CLASS_NAME + ".findCalibrationPattern(): OpenCV is not ready or failed to load."); return null; } } -} \ No newline at end of file + + @Override + public void calibrateCamera(float[][] calibrationSamples, byte[] frame) { + if(ocvOn){ + float[] calibrationPoints = new float[ProjectConstants.CALIBRATION_PATTERN_POINTS * 2 * ProjectConstants.CALIBRATION_SAMPLES]; + int w = ProjectConstants.CALIBRATION_PATTERN_POINTS * 2; + Bitmap tFrame; + Mat inImg = new Mat(); + + // Save the calibration points on a one dimensional array for easier parameter passing + // to the native code. + for(int i = 0; i < ProjectConstants.CALIBRATION_SAMPLES; i++){ + for(int j = 0, p = 0; j < ProjectConstants.CALIBRATION_PATTERN_POINTS; j++, p += 2){ + calibrationPoints[p + (w * i)] = calibrationSamples[i][p]; + calibrationPoints[(p + 1) + (w * i)] = calibrationSamples[i][p + 1]; + } + } + + // Decode the input image and convert it to an OpenCV matrix. + tFrame = BitmapFactory.decodeByteArray(frame, 0, frame.length); + Utils.bitmapToMat(tFrame, inImg); + + // Attempt to obtain the camera parameters. + double error = calibrateCameraParameters(cameraMatrix.getNativeObjAddr(), distortionCoeffs.getNativeObjAddr(), inImg.getNativeObjAddr(), calibrationPoints); + Gdx.app.log(TAG, CLASS_NAME + "calibrateCamera(): calibrateCameraParameters retured " + Double.toString(error)); + cameraCalibrated = true; + + }else{ + Gdx.app.debug(TAG, CLASS_NAME + ".calibrateCamera(): OpenCV is not ready or failed to load."); + } + } + + @Override + public byte[] undistortFrame(byte[] frame){ + if(ocvOn){ + if(cameraCalibrated){ + byte undistortedFrame[]; + Bitmap tFrame, mFrame; + Mat inImg = new Mat(), outImg = new Mat(); + + // Decode the input frame and convert it to an OpenCV Matrix. + tFrame = BitmapFactory.decodeByteArray(frame, 0, frame.length); + Utils.bitmapToMat(tFrame, inImg); + + // Apply the undistort correction to the input frame. + Imgproc.undistort(inImg, outImg, cameraMatrix, distortionCoeffs); + + // Encode the output image as a JPEG image. + mFrame = Bitmap.createBitmap(outImg.cols(), outImg.rows(), Bitmap.Config.RGB_565); + Utils.matToBitmap(outImg, mFrame); + mFrame.compress(CompressFormat.JPEG, 100, outputStream); + + // Prepare the return frame. + undistortedFrame = outputStream.toByteArray(); + + // Clean up memory. + tFrame.recycle(); + mFrame.recycle(); + outputStream.reset(); + + return undistortedFrame; + + }else{ + Gdx.app.debug(TAG, CLASS_NAME + ".undistortFrame(): Camera has not been calibrated."); + return null; + } + }else{ + Gdx.app.debug(TAG, CLASS_NAME + ".undistortFrame(): OpenCV is not ready or failed to load."); + return null; + } + } + + @Override + public boolean isCameraCalibrated() { + return ocvOn && cameraCalibrated; + } + + @Override + public float getFocalPointX() { + return ocvOn && cameraCalibrated ? (float)cameraMatrix.get(0, 0)[0] : 0.0f; + } + + @Override + public float getFocalPointY() { + return ocvOn && cameraCalibrated ? (float)cameraMatrix.get(1, 1)[0] : 0.0f; + } + + @Override + public float getCameraCenterX() { + return ocvOn && cameraCalibrated ? (float)cameraMatrix.get(0, 2)[0] : 0.0f; + } + + @Override + public float getCameraCenterY() { + return ocvOn && cameraCalibrated ? (float)cameraMatrix.get(1, 2)[0] : 0.0f; + } +}