NDK​系列之OpenGL与OpenCV实现大眼萌特效,本节主要是在上一节OpenGL仿抖音极快极慢录制特效视频上增加大眼萌的特效。

OpenGL视频特效系列:

NDK OpenGL渲染画面效果

NDK OpenGL离屏渲染与工程代码整合

NDK OpenGL仿抖音极快极慢录制特效视频

NDK OpenGL与OpenCV实现大眼萌特效

NDK OpenGL实现美颜功能

实现效果:

实现逻辑:

1.集成OpenCV,实现检测人脸框框;

2.集成中科院FaceAlignment,在人脸框框基础上,进行人脸关键点定位,有5个点

3.用C++构建实例化Face.java(人脸类,包含关键点5个点坐标值)

4.通过MyGLRenderer自定义渲染器触发着色器执行放大眼睛效果。

一、OpenCV集成到AS

1)复制OpenCV源文件到cpp目录下,动态库文件复制到jniLibs目录下:

2)在CMakeLists文件中,导入源文件和库文件

二、集成中科院FaceAlignment

1)开源的 SeetaFace 人脸识别引擎是由中科院计算所山世光研究员带领的人脸识别研究组研发。代码基于 C++实现,且不依赖于任何第三方的库函。SeetaFace 人脸识别引擎包括了搭建一套全自动人脸识别系统所需的三个核心模块,即:人脸检测模块(SeetaFace Detection)、面部特征点定位模块(SeetaFaceAlignment)以及人脸特征提取与比对模块(SeetaFace Identification)。
SeetaFace 项目网址为 https://github.com/seetaface/SeetaFaceEngin 
我们这里只需要定位人眼,集成的是面部特征点定位模块(SeetaFaceAlignment),复制FaceAlignment源文件到cpp目录下:

2)配置FaceAlignment源文件中的CMakeLists文件

3)在CMakeLists文件中,导入源文件和库文件

三、人脸跟踪/人脸关键点定位代码实现

1)创建java层的Face类,用于保存人脸关键点的坐标等信息

public class Face {/*** 人脸框的x和y,不等于 width,height,所以还是单独定义算了(没啥关联)* float[] landmarks 细化后如下:12个元素* 0下标(保存:人脸框的 x)* 1下标(保存:人脸框的 y)* <p>* 2下标(保存:左眼x)* 3下标(保存:左眼y)* <p>* 4下标(保存:右眼x)‘* 5下标(保存:右眼y)* <p>* 6下标(保存:鼻尖x)* 7下标(保存:鼻尖y)* <p>* 8下标(保存:左边嘴角x)* 9下标(保存:左边嘴角y)* <p>* 10下标(保存:右边嘴角x)* 11下标(保存:右边嘴角y)*/public float[] landmarks;public int width;        // 保存人脸的框 的宽度public int height;       // 保存人脸的框 的高度public int imgWidth;    // 送去检测的所有宽 屏幕public int imgHeight;   // 送去检测的所有高 屏幕public Face(int width, int height, int imgWidth, int imgHeight, float[] landmarks) {this.landmarks = landmarks;this.width = width;this.height = height;this.imgWidth = imgWidth;this.imgHeight = imgHeight;}
}

2)创建Java层FaceTrack.java人脸追踪类,人脸与关键点的定位追踪 api 类, 与C++层交互。

public class FaceTrack {static {System.loadLibrary("native-lib");}private CameraHelper mCameraHelper; // 手机相机预览工具类(之前的内容)private Handler mHandler; // 此Handler方便开启一个线程private HandlerThread mHandlerThread; // 此HandlerThread方便开启一个线程private long self; // FaceTrack.cpp对象的地址指向long值private Face mFace; // 最终人脸跟踪的结果/*** @param model        OpenCV人脸的模型的文件路径* @param seeta        中科院的那个模型(五个关键点的特征点的文件路径)* @param cameraHelper 需要把CameraID传递给C++层*/public FaceTrack(String model, String seeta, CameraHelper cameraHelper) {mCameraHelper = cameraHelper;self = native_create(model, seeta); // 传入人脸检测模型到C++层处理,返回FaceTrack.cpp的地址指向// 开启一个线程:去执行 人脸检测定位mHandlerThread = new HandlerThread("FaceTrack");mHandlerThread.start();mHandler = new Handler(mHandlerThread.getLooper()) {@Overridepublic void handleMessage(Message msg) {// 子线程 耗时再久 也不会对其他地方 (如:opengl绘制线程) 产生影响synchronized (FaceTrack.this) {// 定位 线程中检测Log.i("FaceTrack", "开始人脸定位 线程中检测");mFace = native_detector(self, (byte[]) msg.obj, mCameraHelper.getCameraID(), 800, 480);if (mFace != null) {Log.e("拍摄了人脸mFace.toString:", mFace.toString()); // 看看打印效果}}}};}public void startTrack() { // 启动跟踪器 OpenCVnative_start(self);}public void stopTrack() { // 停止跟踪器 OpenCVsynchronized (this) {mHandlerThread.quitSafely();mHandler.removeCallbacksAndMessages(null);native_stop(self);self = 0;}}// 开始检测人脸 byte[] data == NV21 Camera的数据 byte[]public void detector(byte[] data) { // 要把相机的数据,给C++层做人脸追踪// 把积压的 11号任务移除掉mHandler.removeMessages(11);// 加入新的11号任务Message message = mHandler.obtainMessage(11);message.obj = data;mHandler.sendMessage(message);}public Face getFace() { // 这个函数很重要return mFace; // 如果能拿到mFace,就证明 有人脸最终信息 和 5个关键点信息}/*** 传入人脸检测模型到C++层处理** @param model OpenCV人脸模型* @param seeta Seeta中科院的人脸关键点模型* @return FaceTrack.cpp地址指向long值*/private native long native_create(String model, String seeta);private native void native_start(long self); // 开始追踪private native void native_stop(long self); // 停止追踪/*** 执行真正的人脸探测工作** @param self     Face.java对象的地址指向long值* @param data     Camera相机 byte[] data NV21摄像头的数据* @param cameraId Camera相机ID,前置摄像头,后置摄像头* @param width    宽度* @param height   高度* @return 若Face==null:代表没有人脸信息+人脸5特征,  若Face有值:人脸框x/y,+ 5个特侦点(本次只需要 人脸框x/y + 双眼关键点)*/private native Face native_detector(long self, byte[] data, int cameraId, int width, int height);
}

C++层初始化OpenCV和Seeta人脸关键点定位

Java_com_ndk_opengl_face_FaceTrack_native_1create(JNIEnv *env, jobject thiz, jstring model_,jstring seeta_) {const char *model = env->GetStringUTFChars(model_, 0);const char *seeta = env->GetStringUTFChars(seeta_, 0);FaceTrack *faceTrack = new FaceTrack(model, seeta);env->ReleaseStringUTFChars(model_, model);env->ReleaseStringUTFChars(seeta_, seeta);return reinterpret_cast<jlong>(faceTrack);
}
FaceTrack::FaceTrack(const char *model, const char *seeta) {Ptr<CascadeDetectorAdapter> mainDetector = makePtr<CascadeDetectorAdapter>(makePtr<CascadeClassifier>(model)); // OpenCV主探测器Ptr<CascadeDetectorAdapter> trackingDetector = makePtr<CascadeDetectorAdapter>(makePtr<CascadeClassifier>(model)); // OpenCV跟踪探测器DetectionBasedTracker::Parameters detectorParams;// OpenCV创建追踪器,为了下面的(开始跟踪,停止跟踪)tracker = makePtr<DetectionBasedTracker>(mainDetector, trackingDetector, detectorParams);// TODO >>>>>>>>>>>>>>>>>>>>>>> 上面是OpenCV模板代码人脸追踪区域, 下面是Seeta人脸关键点代码+OpenCV >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>faceAlignment = makePtr<seeta::FaceAlignment>(seeta); // Seeta中科院关键特征点
}

3)当Surface改变时,会回调到自定义渲染器MyGlRendere的onSurfaceChanged()函数,在这里创建人脸检测跟踪器;

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {Log.i(TAG, "onSurfaceChanged");mWidth = width;mHeight = height;// 创建人脸检测跟踪器 // TODO 【大眼相关代码】mFaceTrack = new FaceTrack(modelPath, seetaPath, mCameraHelper);mFaceTrack.startTrack(); // 启动跟踪器mCameraHelper.startPreview(mSurfaceTexture); // 开始预览mCameraFilter.onReady(width, height);mScreenFilter.onReady(width, height);
}

C++层OpenCV开启追踪器

extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_opengl_face_FaceTrack_native_1start(JNIEnv *env, jobject thiz, jlong self) {if (self == 0) {return;}FaceTrack *faceTrack = reinterpret_cast<FaceTrack *>(self);faceTrack->startTracking();
}

4)当Camera画面有数据时,会回调到自定义渲染器MyGlRendere的onPreviewFrame()函数,在这里真正开始检测人脸,把相机的数据,给C++层做人脸追踪;

@Override
public void onPreviewFrame(byte[] data, Camera camera) {if (mFaceTrack != null)mFaceTrack.detector(data);
}

C++层执行人脸追踪

extern "C"
JNIEXPORT jobject JNICALL
Java_com_ndk_opengl_face_FaceTrack_native_1detector(JNIEnv *env, jobject thiz, jlong self,jbyteArray data_, jint camera_id,jint width, jint height) {if (self == 0) {return NULL;}jbyte *data = env->GetByteArrayElements(data_, 0);FaceTrack *faceTrack = reinterpret_cast<FaceTrack *>(self); // 通过地址反转CPP对象LOGI("OpenCV旋转数据操作");// OpenCV旋转数据操作Mat src(height * 3 / 2, width, CV_8UC1, data); // 摄像头数据data 转成 OpenCv的 Mat// imwrite("/sdcard/camerajin.jpg", src); // 做调试的时候用的(方便查看:有没有摆正,有没有灰度化 等)cvtColor(src, src, CV_YUV2RGBA_NV21); // 把YUV转成RGBALOGI("OpenCV旋转数据操作 camera_id %d", camera_id);if (camera_id == 1) { // 前摄rotate(src, src, ROTATE_90_COUNTERCLOCKWISE); // 逆时针90度flip(src, src, 1); // y 轴 翻转(镜像操作)} else {  // 后摄rotate(src, src, ROTATE_90_CLOCKWISE);}LOGI("OpenCV基础操作");// OpenCV基础操作cvtColor(src, src, COLOR_RGBA2GRAY); // 灰度化equalizeHist(src, src); // 均衡化处理(直方图均衡化,增强对比效果)vector<Rect2f> rects;faceTrack->detector(src, rects); // 送去定位,要去做人脸的检测跟踪了env->ReleaseByteArrayElements(data_, data, 0);// rects 他已经有丰富的人脸框框的信息,接下来就是,关键点定位封装操作Face.java// TODO 注意:上面的代码执行完成后,就拿到了 人脸检测的成果 放置在rects中// C++ 反射 实例化 Face.java 并且保证 Face.java有值int imgWidth = src.cols; // 构建 Face.java的 int imgWidth; 送去检测图片的宽int imgHeight = src.rows; // 构建 Face.java的 int imgHeight; 送去检测图片的高int ret = rects.size(); // 如果有一个人脸,那么size肯定大于0LOGI("OpenCV基础操作 ret %d", ret);if (ret) { // 注意:有人脸,才会进ifjclass clazz = env->FindClass("com/ndk/opengl/face/Face");jmethodID construct = env->GetMethodID(clazz, "<init>", "(IIII[F)V");// int width, int height,int imgWidth,int imgHeight, float[] landmarkint size = ret * 2; // 乘以2是因为,有x与y, 其实size===2,因为rects就一个人脸// 构建 Face.java的 float[] landmarks;jfloatArray floatArray = env->NewFloatArray(size);for (int i = 0, j = 0; i < size; ++j) {  // 前两个就是人脸的x与yfloat f[2] = {rects[j].x, rects[j].y};env->SetFloatArrayRegion(floatArray, i, 2, f);i += 2;}Rect2f faceRect = rects[0];int faceWidth = faceRect.width; // 构建 Face.java的 int width; 保存人脸的宽int faceHeight = faceRect.height; // 构建 Face.java的 int height; 保存人脸的高// 实例化Face.java对象,都是前面JNI课程的基础jobject face = env->NewObject(clazz, construct, faceWidth, faceHeight, imgWidth, imgHeight,floatArray);rectangle(src, faceRect, Scalar(0, 0, 255)); // OpenCV内容for (int i = 1; i < ret; ++i) { // OpenCV内容circle(src, Point2f(rects[i].x, rects[i].y), 5, Scalar(0, 255, 0));}imwrite("/sdcard/srcjin.jpg", src); // 做调试的时候用的(方便查看:有没有摆正,有没有灰度化 等)return face; // 返回 jobject == Face.java(已经有值了,有人脸所有的信息了,那么就可以开心,放大眼睛)}src.release(); // Mat释放工作return NULL;
}

四、开启大眼效果

1)单独创建大眼过滤器BigEyeFilter,方便后扩展,每增加一种新特效就新增一个过滤器来实现;

public class BigEyeFilter extends BaseFrameFilter {private int left_eye; // 左眼坐标的属性索引private int right_eye; // 右眼坐标的属性索引private FloatBuffer left; // 左眼的bufferprivate FloatBuffer right; // 右眼的bufferprivate Face mFace; // 人脸追踪+人脸5关键点 最终的成果public BigEyeFilter(Context context) {super(context, R.raw.base_vertex, R.raw.bigeye_fragment);left_eye = glGetUniformLocation(mProgramId, "left_eye"); // 左眼坐标的属性索引right_eye = glGetUniformLocation(mProgramId, "right_eye"); // 右眼坐标的属性索引left = ByteBuffer.allocateDirect(2 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();  // 左眼buffer申请空间right = ByteBuffer.allocateDirect(2 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer(); // 右眼buffer申请空间}@Overridepublic int onDrawFrame(int textureID) {if (null == mFace) {return textureID; // 如果这个对象为null,证明没有检测到人脸,啥事都不用做}// 1:设置视窗glViewport(0, 0, mWidth, mHeight);// 这里是因为要渲染到FBO缓存中,而不是直接显示到屏幕上glBindFramebuffer(GL_FRAMEBUFFER, mFrameBuffers[0]);// 2:使用着色器程序glUseProgram(mProgramId);// 渲染 传值// 1:顶点数据mVertexBuffer.position(0);glVertexAttribPointer(vPosition, 2, GL_FLOAT, false, 0, mVertexBuffer); // 传值glEnableVertexAttribArray(vPosition); // 传值后激活// 2:纹理坐标mTextureBuffer.position(0);glVertexAttribPointer(vCoord, 2, GL_FLOAT, false, 0, mTextureBuffer); // 传值glEnableVertexAttribArray(vCoord); // 传值后激活float[] landmarks =  mFace.landmarks; // TODO 传 mFace 眼睛坐标 给着色器/* landmarks[2] / mFace.imgWidth ?landmarks 他的相对位置是,C++层里面得到的坐标,这个坐标是相对屏幕的但是我们的OpenGL纹理坐标才行,因为OpenGL着色器代码 纹理 是 0~1 范围所以需要  landmarks[2] / mFace.imgWidth 转换一下*/// 左眼: 的 x y 值,保存到 左眼buffer中float x = landmarks[2] / mFace.imgWidth;float y = landmarks[3] / mFace.imgHeight;left.clear();left.put(x);left.put(y);left.position(0);glUniform2fv(left_eye, 1, left);// 右眼: 的 x y 值,保存到 右眼buffer中x = landmarks[4] / mFace.imgWidth;y = landmarks[5] / mFace.imgHeight;right.clear();right.put(x);right.put(y);right.position(0);glUniform2fv(right_eye, 1, right);// 片元 vTextureglActiveTexture(GL_TEXTURE0); // 激活图层glBindTexture(GL_TEXTURE_2D, textureID); // 绑定glUniform1i(vTexture, 0); // 传递参数glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // 通知opengl绘制// 解绑fboglBindTexture(GL_TEXTURE_2D, 0);glBindFramebuffer(GL_FRAMEBUFFER, 0);return mFrameBufferTextures[0];//返回fbo大眼后的纹理id}public void setFace(Face mFace) { // C++层把人脸最终5关键点成果的(mFaceTrack.getFace()) 赋值给此函数this.mFace = mFace;}
}

大眼的片元着色器代码,使用局部放大的算法实现

// TODO 片元着色器(大眼专用的, 局部放大的算法)// 着色器坐标: 0 ~ 1precision mediump float; // float 数据的精度varying vec2 aCoord; // 顶点着色器传过来的 采样点的坐标
uniform sampler2D vTexture; // 采样器uniform vec2 left_eye; // 左眼 x/y
uniform vec2 right_eye; // 右眼 x/y// 着色器代码,最好加 .0,防止有问题
// 把公式转成着色器代码
// r:    原来的点 距离眼睛中心点距离(半径)
// rmax: 局部放大 最大半径 / 2
float fs(float r, float rmax) {float a = 0.8; // 放大系数,如果你的a==0,我会直接返回r(啥事不做)// 内置函数:求平方 powreturn (1.0 - pow(r / rmax - 1.0, 2.0) * a);
}// TODO 目的:把正常眼睛的纹理坐标,搬到 放大区域   纹理坐标搬到外面
// oldCoord 整个屏幕的纹理坐标
// eye 眼睛坐标
// rmax: 局部放大 最大半径 / 2
vec2 newCoord(vec2 oldCoord, vec2 eye, float rmax) {vec2 newCoord = oldCoord;float r = distance(oldCoord, eye); // 求两点之间的距离// 必须是眼睛范围才做事情,if (r > 0.0f && r < rmax) { // 如果进不来if,那么还是返回原来的点,啥事不做float fsr = fs(r, rmax);//    新点 - 眼睛     /  老点 - 眼睛   = 新距离;// (newCoord - eye) / (coord - eye) = fsr;// newCoord新点 =    新距离  * (老点     - 眼睛)  +  眼睛newCoord       =    fsr    * (oldCoord - eye)   +  eye;}return newCoord;
}// 那个max应该是可以随便设置的吧,配置一半的限制,是为了避免两眼重叠很奇怪void main(){// gl_FragColor = texture2D(vTexture, aCoord);// 两眼间距的一半  识别区域宽度/2吗float rmax = distance(left_eye, right_eye) / 2.0; // distance 求两点的距离(rmax两眼间距) 注意是放大后的间距// aCoord是整副图像,vec2 newCoord = newCoord(aCoord, left_eye, rmax); // 求左眼放大位置的采样点newCoord = newCoord(newCoord, right_eye, rmax); // 求右眼放大位置的采样点// 此newCoord就是大眼像素坐标值gl_FragColor = texture2D(vTexture, newCoord);
}

2)用户操作UI开启大眼效果

((CheckBox) findViewById(R.id.chk_bigeye)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {@Overridepublic void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {mGLSurfaceView.enableBigEye(isChecked);}
});

将事件分发到自定义渲染器MyGlRendere,在渲染器统一处理效果,开启大眼特效

public void enableBigEye(final boolean isChecked) {// BigEyeFilter bigEyeFilter = new BigEyeFilter(); // 这样可以吗  不行,必须在EGL线程里面绘制myGLSurfaceView.queueEvent(new Runnable() { // 把大眼渲染代码,加入到, GLSurfaceView 的 内置EGL 的 GLTHread里面public void run() {if (isChecked) {mBigEyeFilter = new BigEyeFilter(myGLSurfaceView.getContext());mBigEyeFilter.onReady(mWidth, mHeight);} else {mBigEyeFilter.release();mBigEyeFilter = null;}}});
}

3)相机绘制一帧图像时,会回调到自定义渲染器MyGlRendere的onDrawFrame()函数,获取纹理对象的图像数据,先通过CamreaFilter相机过滤器,实现相关效果,再将其FBO的纹理ID传递给大眼过滤器BigEyeFilter,大眼过滤器增加完特效后,又将包含大眼特效的纹理ID,传递给ScreenFilter屏幕过滤器,将最终成果的纹理ID通过OpenGL渲染到屏幕;

@Override
public void onDrawFrame(GL10 gl) {Log.i(TAG, "onDrawFrame");// 每次清空之前的:例子:上课擦黑白 是一个道理glClearColor(255, 0, 0, 0); // 屏幕清理成颜色 红色,清理成红色的黑板一样// mask 细节看看此文章:https://blog.csdn.net/z136411501/article/details/83273874// GL_COLOR_BUFFER_BIT 颜色缓冲区// GL_DEPTH_BUFFER_BIT 深度缓冲区// GL_STENCIL_BUFFER_BIT 模型缓冲区glClear(GL_COLOR_BUFFER_BIT);// 绘制摄像头数据mSurfaceTexture.updateTexImage();  // 将纹理图像更新为图像流中最新的帧数据【刷新一下】// 画布,矩阵数据,通过Native层将数据存储到mtxmSurfaceTexture.getTransformMatrix(mtx);// 相机过滤器,绘制一帧图像,不可见mCameraFilter.setMatrix(mtx);int textureId = mCameraFilter.onDrawFrame(mTextureID[0]); // 摄像头,矩阵,都已经做了// 增加其他特效/*textureId = 美白.onDrawFrame(textureId);textureId = 大眼.onDrawFrame(textureId);textureId = xxx.onDrawFrame(textureId);*/// TODO 【大眼相关代码】 textureId = 大眼Filter.onDrawFrame(textureId);if (null != mBigEyeFilter) {mBigEyeFilter.setFace(mFaceTrack.getFace());textureId = mBigEyeFilter.onDrawFrame(textureId);}// 屏幕过滤器,绘制一帧图像,屏幕显示mScreenFilter.onDrawFrame(textureId); // textureId == 最终成果的纹理IDmMediaRecorder.encodeFrame(textureId, mSurfaceTexture.getTimestamp());
}

大眼过滤器BigEyeFilter绘制大眼效果

@Override
public int onDrawFrame(int textureID) {if (null == mFace) {return textureID; // 如果这个对象为null,证明没有检测到人脸,啥事都不用做}// 1:设置视窗glViewport(0, 0, mWidth, mHeight);// 这里是因为要渲染到FBO缓存中,而不是直接显示到屏幕上glBindFramebuffer(GL_FRAMEBUFFER, mFrameBuffers[0]);// 2:使用着色器程序glUseProgram(mProgramId);// 渲染 传值// 1:顶点数据mVertexBuffer.position(0);glVertexAttribPointer(vPosition, 2, GL_FLOAT, false, 0, mVertexBuffer); // 传值glEnableVertexAttribArray(vPosition); // 传值后激活// 2:纹理坐标mTextureBuffer.position(0);glVertexAttribPointer(vCoord, 2, GL_FLOAT, false, 0, mTextureBuffer); // 传值glEnableVertexAttribArray(vCoord); // 传值后激活float[] landmarks =  mFace.landmarks; // TODO 传 mFace 眼睛坐标 给着色器/* landmarks[2] / mFace.imgWidth ?landmarks 他的相对位置是,C++层里面得到的坐标,这个坐标是相对屏幕的但是我们的OpenGL纹理坐标才行,因为OpenGL着色器代码 纹理 是 0~1 范围所以需要  landmarks[2] / mFace.imgWidth 转换一下*/// 左眼: 的 x y 值,保存到 左眼buffer中float x = landmarks[2] / mFace.imgWidth;float y = landmarks[3] / mFace.imgHeight;left.clear();left.put(x);left.put(y);left.position(0);glUniform2fv(left_eye, 1, left);// 右眼: 的 x y 值,保存到 右眼buffer中x = landmarks[4] / mFace.imgWidth;y = landmarks[5] / mFace.imgHeight;right.clear();right.put(x);right.put(y);right.position(0);glUniform2fv(right_eye, 1, right);// 片元 vTextureglActiveTexture(GL_TEXTURE0); // 激活图层glBindTexture(GL_TEXTURE_2D, textureID); // 绑定glUniform1i(vTexture, 0); // 传递参数glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // 通知opengl绘制// 解绑fboglBindTexture(GL_TEXTURE_2D, 0);glBindFramebuffer(GL_FRAMEBUFFER, 0);return mFrameBufferTextures[0];//返回fbo大眼后的纹理id
}

至此,OpenGL与OpenCV实现大眼萌特效已完成。

源码:

NdkOpenGLPlay: NDK OpenGL渲染画面效果

NDK OpenGL与OpenCV实现大眼萌特效相关推荐

  1. Android使用NDK OpenGL ES3.0绘制一个三角形

    Android使用NDK  OpenGL ES3.0绘制一个三角形 [尊重原创,转载请注明出处]https://blog.csdn.net/guyuealian/article/details/820 ...

  2. Mac 配置支持 opengl 的 opencv 4.2

    本教程教大家如何在Mac环境下对opencv源码进行编译,并开启opengl支持.因为配置过程及其艰辛,基本没有现成教程,希望记录下来能帮到有需要的人.其他系统的其实流程更简单 一.准备工作 open ...

  3. VS配置永久OpenCV(小萌轻松操作):超细致

    VS配置永久OpenCV(小萌轻松操作):超细致 一. 下载安装Visual Studio2022 1.进入:Visual Studio2022的官网,点击Community2022进行下载. 2.双 ...

  4. 名为dash的蓝色插嘴小机器人_Dash Dot儿童智能机器人 | 孩子们的高科技玩具,“家有大眼萌”Dash Dot机器人体验-极果...

    说到这款高级玩具首先要介绍一下这款玩具的开发设计公司Wonder Worshop,2013年,一家名为 Play-i 的初创公司推出了一对编程机器人Bo 和 Yana,专为孩子设计,目的是培养孩子对编 ...

  5. 【OpenCV】图像的特效变换扭曲变换、球形变换、波动变换

    [原创][OpenCV]图像的特效变换扭曲变换.球形变换.波动变换 扭曲变换.球形变换.波动变换 代码如下,每个变换三个结果 什么?你说解释在哪里? 扭曲变换.球形变换.波动变换 话不多说,林忠老师的 ...

  6. opencv中图像处理油画特效

    opencv中图像处理油画特效 import cv2 import numpy as np import random import mathimg = cv2.imread(r"C:\Us ...

  7. OpenGL与OpenCV实现增强现实

    该程序通过OpenCV实现对Marker的识别和定位,然后通过OpenGL将虚拟物体叠加到摄像头图像下,实现增强现实.首先来看看我们使用的Marker: 这是众多Marker中的一个,它们都被一圈的黑 ...

  8. 使用python+OpenCV实现抖音特效“蓝线挑战”

    使用OpenCV实现抖音"蓝线挑战"特效.原理比较简单,当蓝线在视频画面中滑动,然后从滑过的每一帧中截取部分画面生成一幅静态图片,由于上一帧画面已经定格,那么通过调整下一帧画面可以 ...

  9. android逆透视变换坐标,android – 如何使用OpenGL模拟OpenCV的warpPerspective功能(透视变换)...

    我在Python和C中使用OpenCV完成了图像变形,看到可口可乐标志在我选择的角落里扭曲了: 使用以下图像: 还有这个: 我需要做到这一点,但在OpenGL中.我会: >角落里面我要映射扭曲的 ...

最新文章

  1. C++非类型模板参数
  2. 错误:Parameter ‘0‘ not found.Available parameters are [arg1, arg0, param1, param2]的解决方法
  3. linux mint 下载迅雷安装包,linux mint 最新版本下载地址
  4. Oracle数字函数:数字四舍五入,取整以及格式化
  5. sqoop1.4.5 导入 hive IOException running import job: java.io.IOException: Hive exited with status 1
  6. AD管控下的弹性云桌面和文件共享最佳实践
  7. QuickSort简解(分治思想) By ACReaper
  8. dyaddown matlab,matlab 采样函数
  9. PAT乙类1010之1010 一元多项式求导
  10. 在使用avalon框架的时候,用ms-duplex双工绑定,在template上是有数据渲染的,但是js里面却是undefined...
  11. netscreen MIP 问题
  12. dex2jar java 1.8_利用 dex2jar 反编译 dex文件
  13. Excel - 数组类型及数组公式
  14. 嚯,这款AI建模工具实在太强大了,快来pick!
  15. 2022年帝国CMS7.5内核精仿《5288商机网》二次开发多个功能,运营级
  16. 计算机论文期刊文献,j计算机期刊文章参考文献 j计算机参考文献有哪些
  17. 【2016新年版】年度精品 XP,32/64位Win7,32/64位Win8,32/64位Win10系统
  18. vue拖动滑块验证组件
  19. java表示一个数的二进制数,怎么表示一个二进制数?
  20. 基于 javaagent + javassist 一步步实现调用链系统 (2)

热门文章

  1. 贵州支教之第五天(11月11日)
  2. 六月集训(第13天) —— 双向链表
  3. 31款轻量高效的开源 JavaScript 插件和库
  4. 使用Arduino和L293D控制电机的初学者指南
  5. .net分布式压力测试工具(Beetle.DT)
  6. 英语六级638分经验总结
  7. How to Write and Publish a Scientific Paper-Where to Submit Your Manuscript
  8. Phoebe implementation invitational for 2014 tiro 3.0 部分题解
  9. ClickHouse 官方文档摘录
  10. 推荐一下《恋恋三季》的主题曲