以前我写过用opencv实现抠图,但只是简单的画一个矩形,哪些是前景哪些是背景都由opencv自己判断,最近忙完了就研究了一下交互式抠图
主要参考opencv的官方例子,不过官方例子是pc端的,所以就对例子进行了修改
先看看效果,原图
然后是用原来的方法扣的
狗脸都没了,接下来看用交互式抠图的效果
忘了说密集恐惧症慎看了,扣完的效果
嗯,有狗脸了,接下来贴代码,先看布局
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.grabcut.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="确定"
android:onClick="onGrabCut"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="重置"
android:onClick="onReset"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="前景"
android:onClick="onFlags"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="背景"
android:onClick="onFlags"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="范围"
android:onClick="onFlags"/>
</LinearLayout>
</HorizontalScrollView>
<ImageView
android:id="@+id/image_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:maxHeight="1000dp"
android:background="#435434"/>
</LinearLayout>
</android.support.constraint.ConstraintLayout>
ImageView的adjustViewBounds和maxHeight的功能是让ImageView根据图片大小自适应,好方便做手势操作,虽然还是有误差
java代码
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
private long gcapp;
private int flags = 0; // 0范围,1前景,2背景
private Bitmap bitmap;
private ImageView imageView;
private Bitmap bm;
private float s = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
imageView = findViewById(R.id.image_view);
bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.timg);
bm = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Mat img = new Mat();
Utils.bitmapToMat(bitmap, img);
Imgproc.cvtColor(img, img, Imgproc.COLOR_RGBA2RGB);
gcapp = initGrabCut(img.nativeObj);
imageView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(s == 0){
s = imageView.getWidth()*1.0f / bitmap.getWidth();
}
int x = (int) (event.getX()/s);
int y = (int) (event.getY()/s);
int type = event.getAction();
switch (type){
case MotionEvent.ACTION_DOWN:
moveGrabCut(0,x,y,flags,gcapp);
break;
case MotionEvent.ACTION_UP:
moveGrabCut(1,x,y,flags,gcapp);
break;
case MotionEvent.ACTION_CANCEL:
moveGrabCut(1,x,y,flags,gcapp);
break;
case MotionEvent.ACTION_MOVE:
moveGrabCut(2,x,y,flags,gcapp);
break;
}
return true;
}
});
}
public void onFlags(View view){
Button button = (Button) view;
if("范围".equals(button.getText().toString())){
flags = 0;
}else if("前景".equals(button.getText().toString())){
flags = 1;
}else if("背景".equals(button.getText().toString())){
flags = 2;
}
}
public void onReset(View view){
flags = 0;
reset(gcapp);
}
public void onGrabCut(View view){
Thread thread = new Thread(){
@Override
public void run() {
super.run();
Log.d("grabCut","开始处理");
if(grabCut(gcapp)){
runOnUiThread(new Runnable() {
@Override
public void run() {
grabCutOver(gcapp);
}
});
}
Log.d("grabCut","结束处理");
}
};
thread.start();
}
public void showImage(long image){
Mat img = new Mat(image);
Utils.matToBitmap(img,bm);
imageView.setImageBitmap(bm);
}
public native long initGrabCut(long image);
public native void moveGrabCut(int event, int x, int y, int flags,long gcapp);
public native void reset(long gcapp);
public native boolean grabCut(long gcapp);
public native void grabCutOver(long gcapp);
}
c++代码
#include <jni.h>
#include <string>
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace std;
using namespace cv;
const Scalar RED = Scalar(0,0,255);
const Scalar PINK = Scalar(230,130,255);
const Scalar BLUE = Scalar(255,0,0);
const Scalar LIGHTBLUE = Scalar(255,255,160);
const Scalar GREEN = Scalar(0,255,0);
static void getBinMask( const Mat& comMask, Mat& binMask )
{
if( comMask.empty() || comMask.type()!=CV_8UC1 )
CV_Error( Error::StsBadArg, "comMask is empty or has incorrect type (not CV_8UC1)" );
if( binMask.empty() || binMask.rows!=comMask.rows || binMask.cols!=comMask.cols )
binMask.create( comMask.size(), CV_8UC1 );
binMask = comMask & 1;
}
class GCApplication
{
public:
enum{ NOT_SET = 0, IN_PROCESS = 1, SET = 2 };
static const int radius = 20;
static const int thickness = -1;
GCApplication();
~GCApplication();
void reset();
void setImageAndShowId(Mat *_image, jmethodID _showId );
void showImage(JNIEnv *env, jobject instance) const;
void mouseClick( int event, int x, int y, int flags, JNIEnv *env, jobject instance);
int nextIter();
int getIterCount() const { return iterCount; }
private:
void setRectInMask();
void setLblsInMask( int flags, Point p ,bool isPr);
const Mat* image;
jmethodID showId;
Mat mask;
Mat bgdModel, fgdModel;
uchar rectState, lblsState, prLblsState;
bool isInitialized;
Rect rect;
vector<Point> fgdPxls, bgdPxls, prFgdPxls, prBgdPxls;
int iterCount;
};
GCApplication::GCApplication(){
}
GCApplication::~GCApplication(){
}
void GCApplication::reset()
{
if( !mask.empty() )
mask.setTo(Scalar::all(GC_BGD));
bgdPxls.clear(); fgdPxls.clear();
prBgdPxls.clear(); prFgdPxls.clear();
isInitialized = false;
rectState = NOT_SET;
lblsState = NOT_SET;
prLblsState = NOT_SET;
iterCount = 0;
}
void GCApplication::setImageAndShowId(Mat *_image, jmethodID _showId )
{
if( _image->empty())
return;
image = _image;
showId = _showId;
mask.create( image->size(), CV_8UC1);
reset();
}
void GCApplication::showImage(JNIEnv *env, jobject instance) const
{
if( image->empty() )
return;
Mat res;
Mat binMask;
if( !isInitialized )
image->copyTo( res );
else
{
getBinMask( mask, binMask );
image->copyTo( res, binMask );
}
vector<Point>::const_iterator it;
for( it = bgdPxls.begin(); it != bgdPxls.end(); ++it )
circle( res, *it, radius, BLUE, thickness );
for( it = fgdPxls.begin(); it != fgdPxls.end(); ++it )
circle( res, *it, radius, RED, thickness );
for( it = prBgdPxls.begin(); it != prBgdPxls.end(); ++it )
circle( res, *it, radius, LIGHTBLUE, thickness );
for( it = prFgdPxls.begin(); it != prFgdPxls.end(); ++it )
circle( res, *it, radius, PINK, thickness );
if( rectState == IN_PROCESS || rectState == SET )
rectangle( res, Point( rect.x, rect.y ), Point(rect.x + rect.width, rect.y + rect.height ), GREEN, 2);
long img = (long) &res;
env->CallVoidMethod(instance,showId,img);
}
void GCApplication::setRectInMask()
{
CV_Assert( !mask.empty() );
mask.setTo( GC_BGD );
rect.x = max(0, rect.x);
rect.y = max(0, rect.y);
rect.width = min(rect.width, image->cols-rect.x);
rect.height = min(rect.height, image->rows-rect.y);
(mask(rect)).setTo( Scalar(GC_PR_FGD) );
}
void GCApplication::setLblsInMask( int flags, Point p, bool isPr )
{
vector<Point> *bpxls, *fpxls;
uchar bvalue, fvalue;
if( !isPr )
{
bpxls = &bgdPxls;
fpxls = &fgdPxls;
bvalue = GC_BGD;
fvalue = GC_FGD;
}
else
{
bpxls = &prBgdPxls;
fpxls = &prFgdPxls;
bvalue = GC_PR_BGD;
fvalue = GC_PR_FGD;
}
if( flags == 2 )
{
bpxls->push_back(p);
circle( mask, p, radius, bvalue, thickness );
}
if( flags == 1)
{
fpxls->push_back(p);
circle( mask, p, radius, fvalue, thickness );
}
}
// event:DOWN = 0,UP = 1,MOVE = 2
void GCApplication::mouseClick( int event, int x, int y, int flags ,JNIEnv *env, jobject instance)
{
// TODO add bad args check
switch(event){
case 0: {
if (flags == 0 && rectState == NOT_SET) {
rectState = IN_PROCESS;
rect = Rect(x, y, 1, 1);
}
if ( flags == 1 && rectState == SET )
lblsState = IN_PROCESS;
if ( flags == 2 && rectState == SET )
prLblsState = IN_PROCESS;
}
break;
case 1:{
if(flags == 0 || flags == 1){
if( rectState == IN_PROCESS )
{
rect = Rect( Point(rect.x, rect.y), Point(x,y) );
rectState = SET;
setRectInMask();
CV_Assert( bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty() );
showImage(env,instance);
}
if( lblsState == IN_PROCESS )
{
setLblsInMask(flags, Point(x,y),false);
lblsState = SET;
showImage(env,instance);
}
}
if(flags == 2 && prLblsState == IN_PROCESS ){
setLblsInMask(flags, Point(x,y),false);
prLblsState = SET;
showImage(env,instance);
}
}
break;
case 2:{
if( rectState == IN_PROCESS )
{
rect = Rect( Point(rect.x, rect.y), Point(x,y) );
CV_Assert( bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty() );
showImage(env,instance);
}
else if( lblsState == IN_PROCESS )
{
setLblsInMask(flags, Point(x,y),false);
showImage(env,instance);
}
else if( prLblsState == IN_PROCESS )
{
setLblsInMask(flags, Point(x,y),false);
showImage(env,instance);
}
}
break;
}
}
int GCApplication::nextIter()
{
if( isInitialized )
grabCut( *image, mask, rect, bgdModel, fgdModel, 1 );
else
{
if( rectState != SET )
return iterCount;
if( lblsState == SET || prLblsState == SET )
grabCut( *image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_MASK );
else
grabCut( *image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_RECT );
isInitialized = true;
}
iterCount++;
bgdPxls.clear(); fgdPxls.clear();
prBgdPxls.clear(); prFgdPxls.clear();
return iterCount;
}
;
static void on_mouse(GCApplication *gcapp, int event, int x, int y, int flags,JNIEnv *env, jobject instance)
{
gcapp->mouseClick( event, x, y, flags ,env,instance);
}
extern "C"
JNIEXPORT GCApplication * JNICALL
Java_com_example_grabcut_MainActivity_initGrabCut(JNIEnv *env, jobject instance, jlong image) {
// TODO
Mat *img = (Mat *) image ;
GCApplication *gcapp = new GCApplication();
jclass jc = env->GetObjectClass(instance);
jmethodID showId = env->GetMethodID(jc, "showImage", "(J)V");
gcapp->setImageAndShowId(img, showId);
gcapp->showImage(env,instance);
return gcapp;
}extern "C"
JNIEXPORT void JNICALL
Java_com_example_grabcut_MainActivity_moveGrabCut(JNIEnv *env, jobject instance, jint event, jint x,
jint y, jint flags, jlong gcapp) {
// TODO
GCApplication *g = (GCApplication *) gcapp;
on_mouse(g,event,x,y,flags,env,instance);
}extern "C"
JNIEXPORT void JNICALL
Java_com_example_grabcut_MainActivity_reset(JNIEnv *env, jobject instance, jlong gcapp) {
// TODO
GCApplication *g = (GCApplication *) gcapp;
g->reset();
g->showImage(env,instance);
}extern "C"
JNIEXPORT jboolean JNICALL
Java_com_example_grabcut_MainActivity_grabCut(JNIEnv *env, jobject instance, jlong gcapp) {
// TODO
GCApplication *g = (GCApplication *) gcapp;
int iterCount = g->getIterCount();
int newIterCount = g->nextIter();
return (jboolean) (newIterCount > iterCount);
}extern "C"
JNIEXPORT void JNICALL
Java_com_example_grabcut_MainActivity_grabCutOver(JNIEnv *env, jobject instance, jlong gcapp) {
// TODO
GCApplication *g = (GCApplication *) gcapp;
g->showImage(env,instance);
}
可以看出来,基本代码和官方的一样就是改了操作代码
操作顺序要说一下,要先圈范围,之后可以选前景或背景了,点完后按确定,重置完后也是先选范围,当然这个顺序是我随便写写的,不是一定要这样的顺序,可以自己修改代码,范围抠图和交互抠图也可以独立使用,并没有先后使用的顺序
密码:p44s
2020/4/16更新
想把demo上传到github,可是发现原来的demo不停报错,也不知道为啥,以前测试都没问题啊,就又改改,问题根源在java和c++的内存管理,他们是相互独立的,比如一个Mat同时存在java和c++的时候,当java把资源释放了,而c++不知道java把那个Mat释放了,c++调用的时候就崩溃了,当然反过来的情况也是一样的,所以就把资源统一由java或c++管理,最好不要混合使用
修改上传到GitHub了,RectActivity是简单的抠图,GCActivity是交互抠图,MainActivity和GrabCut这两个文件是我测试用的请无视
GrabCut
上边的官方链接失效了,我重新找了一下,防止下次又失效先复制下来
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace std;
using namespace cv;
static void help(char** argv)
{
cout << "\nThis program demonstrates GrabCut segmentation -- select an object in a region\n"
"and then grabcut will attempt to segment it out.\n"
"Call:\n"
<< argv[0] << " <image_name>\n"
"\nSelect a rectangular area around the object you want to segment\n" <<
"\nHot keys: \n"
"\tESC - quit the program\n"
"\tr - restore the original image\n"
"\tn - next iteration\n"
"\n"
"\tleft mouse button - set rectangle\n"
"\n"
"\tCTRL+left mouse button - set GC_BGD pixels\n"
"\tSHIFT+left mouse button - set GC_FGD pixels\n"
"\n"
"\tCTRL+right mouse button - set GC_PR_BGD pixels\n"
"\tSHIFT+right mouse button - set GC_PR_FGD pixels\n" << endl;
}
const Scalar RED = Scalar(0,0,255);
const Scalar PINK = Scalar(230,130,255);
const Scalar BLUE = Scalar(255,0,0);
const Scalar LIGHTBLUE = Scalar(255,255,160);
const Scalar GREEN = Scalar(0,255,0);
const int BGD_KEY = EVENT_FLAG_CTRLKEY;
const int FGD_KEY = EVENT_FLAG_SHIFTKEY;
static void getBinMask( const Mat& comMask, Mat& binMask )
{
if( comMask.empty() || comMask.type()!=CV_8UC1 )
CV_Error( Error::StsBadArg, "comMask is empty or has incorrect type (not CV_8UC1)" );
if( binMask.empty() || binMask.rows!=comMask.rows || binMask.cols!=comMask.cols )
binMask.create( comMask.size(), CV_8UC1 );
binMask = comMask & 1;
}
class GCApplication
{
public:
enum{ NOT_SET = 0, IN_PROCESS = 1, SET = 2 };
static const int radius = 2;
static const int thickness = -1;
void reset();
void setImageAndWinName( const Mat& _image, const string& _winName );
void showImage() const;
void mouseClick( int event, int x, int y, int flags, void* param );
int nextIter();
int getIterCount() const { return iterCount; }
private:
void setRectInMask();
void setLblsInMask( int flags, Point p, bool isPr );
const string* winName;
const Mat* image;
Mat mask;
Mat bgdModel, fgdModel;
uchar rectState, lblsState, prLblsState;
bool isInitialized;
Rect rect;
vector<Point> fgdPxls, bgdPxls, prFgdPxls, prBgdPxls;
int iterCount;
};
void GCApplication::reset()
{
if( !mask.empty() )
mask.setTo(Scalar::all(GC_BGD));
bgdPxls.clear(); fgdPxls.clear();
prBgdPxls.clear(); prFgdPxls.clear();
isInitialized = false;
rectState = NOT_SET;
lblsState = NOT_SET;
prLblsState = NOT_SET;
iterCount = 0;
}
void GCApplication::setImageAndWinName( const Mat& _image, const string& _winName )
{
if( _image.empty() || _winName.empty() )
return;
image = &_image;
winName = &_winName;
mask.create( image->size(), CV_8UC1);
reset();
}
void GCApplication::showImage() const
{
if( image->empty() || winName->empty() )
return;
Mat res;
Mat binMask;
if( !isInitialized )
image->copyTo( res );
else
{
getBinMask( mask, binMask );
image->copyTo( res, binMask );
}
vector<Point>::const_iterator it;
for( it = bgdPxls.begin(); it != bgdPxls.end(); ++it )
circle( res, *it, radius, BLUE, thickness );
for( it = fgdPxls.begin(); it != fgdPxls.end(); ++it )
circle( res, *it, radius, RED, thickness );
for( it = prBgdPxls.begin(); it != prBgdPxls.end(); ++it )
circle( res, *it, radius, LIGHTBLUE, thickness );
for( it = prFgdPxls.begin(); it != prFgdPxls.end(); ++it )
circle( res, *it, radius, PINK, thickness );
if( rectState == IN_PROCESS || rectState == SET )
rectangle( res, Point( rect.x, rect.y ), Point(rect.x + rect.width, rect.y + rect.height ), GREEN, 2);
imshow( *winName, res );
}
void GCApplication::setRectInMask()
{
CV_Assert( !mask.empty() );
mask.setTo( GC_BGD );
rect.x = max(0, rect.x);
rect.y = max(0, rect.y);
rect.width = min(rect.width, image->cols-rect.x);
rect.height = min(rect.height, image->rows-rect.y);
(mask(rect)).setTo( Scalar(GC_PR_FGD) );
}
void GCApplication::setLblsInMask( int flags, Point p, bool isPr )
{
vector<Point> *bpxls, *fpxls;
uchar bvalue, fvalue;
if( !isPr )
{
bpxls = &bgdPxls;
fpxls = &fgdPxls;
bvalue = GC_BGD;
fvalue = GC_FGD;
}
else
{
bpxls = &prBgdPxls;
fpxls = &prFgdPxls;
bvalue = GC_PR_BGD;
fvalue = GC_PR_FGD;
}
if( flags & BGD_KEY )
{
bpxls->push_back(p);
circle( mask, p, radius, bvalue, thickness );
}
if( flags & FGD_KEY )
{
fpxls->push_back(p);
circle( mask, p, radius, fvalue, thickness );
}
}
void GCApplication::mouseClick( int event, int x, int y, int flags, void* )
{
// TODO add bad args check
switch( event )
{
case EVENT_LBUTTONDOWN: // set rect or GC_BGD(GC_FGD) labels
{
bool isb = (flags & BGD_KEY) != 0,
isf = (flags & FGD_KEY) != 0;
if( rectState == NOT_SET && !isb && !isf )
{
rectState = IN_PROCESS;
rect = Rect( x, y, 1, 1 );
}
if ( (isb || isf) && rectState == SET )
lblsState = IN_PROCESS;
}
break;
case EVENT_RBUTTONDOWN: // set GC_PR_BGD(GC_PR_FGD) labels
{
bool isb = (flags & BGD_KEY) != 0,
isf = (flags & FGD_KEY) != 0;
if ( (isb || isf) && rectState == SET )
prLblsState = IN_PROCESS;
}
break;
case EVENT_LBUTTONUP:
if( rectState == IN_PROCESS )
{
rect = Rect( Point(rect.x, rect.y), Point(x,y) );
rectState = SET;
setRectInMask();
CV_Assert( bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty() );
showImage();
}
if( lblsState == IN_PROCESS )
{
setLblsInMask(flags, Point(x,y), false);
lblsState = SET;
showImage();
}
break;
case EVENT_RBUTTONUP:
if( prLblsState == IN_PROCESS )
{
setLblsInMask(flags, Point(x,y), true);
prLblsState = SET;
showImage();
}
break;
case EVENT_MOUSEMOVE:
if( rectState == IN_PROCESS )
{
rect = Rect( Point(rect.x, rect.y), Point(x,y) );
CV_Assert( bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty() );
showImage();
}
else if( lblsState == IN_PROCESS )
{
setLblsInMask(flags, Point(x,y), false);
showImage();
}
else if( prLblsState == IN_PROCESS )
{
setLblsInMask(flags, Point(x,y), true);
showImage();
}
break;
}
}
int GCApplication::nextIter()
{
if( isInitialized )
grabCut( *image, mask, rect, bgdModel, fgdModel, 1 );
else
{
if( rectState != SET )
return iterCount;
if( lblsState == SET || prLblsState == SET )
grabCut( *image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_MASK );
else
grabCut( *image, mask, rect, bgdModel, fgdModel, 1, GC_INIT_WITH_RECT );
isInitialized = true;
}
iterCount++;
bgdPxls.clear(); fgdPxls.clear();
prBgdPxls.clear(); prFgdPxls.clear();
return iterCount;
}
GCApplication gcapp;
static void on_mouse( int event, int x, int y, int flags, void* param )
{
gcapp.mouseClick( event, x, y, flags, param );
}
int main( int argc, char** argv )
{
cv::CommandLineParser parser(argc, argv, "{@input| messi5.jpg |}");
help(argv);
string filename = parser.get<string>("@input");
if( filename.empty() )
{
cout << "\nDurn, empty filename" << endl;
return 1;
}
Mat image = imread(samples::findFile(filename), IMREAD_COLOR);
if( image.empty() )
{
cout << "\n Durn, couldn't read image filename " << filename << endl;
return 1;
}
const string winName = "image";
namedWindow( winName, WINDOW_AUTOSIZE );
setMouseCallback( winName, on_mouse, 0 );
gcapp.setImageAndWinName( image, winName );
gcapp.showImage();
for(;;)
{
char c = (char)waitKey(0);
switch( c )
{
case '\x1b':
cout << "Exiting ..." << endl;
goto exit_main;
case 'r':
cout << endl;
gcapp.reset();
gcapp.showImage();
break;
case 'n':
int iterCount = gcapp.getIterCount();
cout << "<" << iterCount << "... ";
int newIterCount = gcapp.nextIter();
if( newIterCount > iterCount )
{
gcapp.showImage();
cout << iterCount << ">" << endl;
}
else
cout << "rect must be determined>" << endl;
break;
}
}
exit_main:
destroyWindow( winName );
return 0;
}