編輯:關於Android編程
這段時間微軟的HowOldRobot 測試年齡的網站非常火,訪問量已經爆棚了!不過,這個測試也有很多比較坑爹的地方。比如:。。。。。
再比如。。。
好了 言歸正傳!今天我們就來看看android中怎麼利用人臉識別功能來實現我們自己的HowOld APP
(PS:本人也是借鑒了網上大神的視頻和資料 然後自己加以改進)
想要使用人臉識別功能,我們需要調用Face++中的一些API來完成工作。Face++的官網地址是:http://www.faceplusplus.com.cn/
使用Face++有幾個步驟:
1.注冊賬號
2.創建應用
3.復制 Key 和 Sercret
4.下載SDK
5.將SDK放入我們的工程lib目錄中<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPrrDwcsg17yxuLmk1/fX9s3qwcujrL3Tz8LAtL7Nv6rKvLHg0LTO0sPHtcSzzNDywcuhozwvcD4NCjxoMiBpZD0="布局">布局
首先是布局文件。下面是我們的界面視圖。很簡單的布局,上邊一張圖片 ,下邊一個TextView加三個按鈕 。沒有太多好說的。
MainActivity.xml
接下來, 我們需要編寫一個人臉識別工具類,根據我們傳入的圖片來進行識別並返回數據。其中Constact是我們的常量工具類,存放我們應用的Key和Secret.
FaceRecognize.class
public class FaceRecognize
{
// 回調接口
public interface CallBack
{
// 識別成功
void success(JSONObject result);
// 識別失敗
void error(FaceppParseException e);
}
// 開始識別
public static void detect(final Bitmap bitmap, final CallBack callback)
{
new Thread(new Runnable()
{
@Override
public void run()
{
HttpRequests request = new HttpRequests(Constant.KEY,
Constant.SECRET, true, true);
Bitmap bmSmall = Bitmap.createBitmap(bitmap, 0, 0,
bitmap.getWidth(), bitmap.getHeight());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//壓縮圖片
bmSmall.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] datas = baos.toByteArray();
PostParameters params = new PostParameters();
params.setImg(datas);
try
{
//如果識別成功,調用success回調函數
JSONObject result = request.detectionDetect(params);
if (callback != null)
{
callback.success(result);
}
} catch (FaceppParseException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
if (callback != null)
{
callback.error(e);
Log.e(JSONObject, e.toString());
}
}
}
}).start();
}
}
然後我們在MainActivity中實現我們的主要邏輯。
MainActivity.class
public class MainActivity extends Activity implements OnClickListener
{
//從相冊選擇照片
private static final int PICK_CODE = 0X110;
//照相
private static final int TAKE_PICTURE = 0X114;
//識別成功
private static final int MSG_SUCCESS = 0X111;
//識別失敗
private static final int MSG_ERROR = 0X112;
//剪裁圖片
private static final int CROP_PHOTO = 0x115;
private ImageButton detect, camera, photo;
private TextView tip, ageAndgender;
private ImageView img;
private String mCurrentPhotoPath;
private Bitmap mPhotoImg;
private Canvas mCanvas;
private Paint mPaint;
//自定義對話框
private CustomProgressDialog dialog;
private Uri imageUri;
private String filename;
private boolean isCamera =false;
private Dialog dialogs;
private Handler mHandler = new Handler()
{
public void handleMessage(android.os.Message msg)
{
switch (msg.what)
{
//解析成功
case MSG_SUCCESS:
dialog.dismiss();
//獲取JSON數據
JSONObject result = (JSONObject) msg.obj;
//解析JSON數據
parseResult(result);
img.setImageBitmap(mPhotoImg);
break;
//解析失敗
case MSG_ERROR:
dialog.dismiss();
String errorMsg = (String) msg.obj;
if (TextUtils.isEmpty(errorMsg))
{
tip.setText(Error!!);
}
break;
default:
break;
}
};
};
在上面的代碼片段中,我們定義了一些常量和控件,並且使用handler來處理 識別成功和識別失敗兩種情況。
/**
* 解析JSON數據
* @param object
*/
private void parseResult(JSONObject object)
{
Bitmap bitmap = Bitmap.createBitmap(mPhotoImg.getWidth(),
mPhotoImg.getHeight(), mPhotoImg.getConfig());
mCanvas = new Canvas(bitmap);
mCanvas.drawBitmap(mPhotoImg, 0, 0, null);
mPaint.setColor(Color.RED);
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(3);
mPaint.setStrokeCap(Cap.ROUND);
JSONArray faces;
try
{
faces = object.getJSONArray(face);
int faceCount = faces.length();
//未識別出人臉
if (faceCount == 0)
{
dialogs = new AlertDialog.Builder(this)
.setTitle(檢測結果)
.setMessage(長得太抽象o(╯□╰)o,識別不出來)
.setNegativeButton(重來,
new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog,
int which)
{
dialogs.dismiss();
}
}).create();
dialogs.show();
return;
}
tip.setText(發現 + faceCount + 只 臉 );
//循環處理每一張臉
for (int i = 0; i < faceCount; i++)
{
JSONObject face = faces.getJSONObject(i);
JSONObject position = face.getJSONObject(position);
//臉部中心點X坐標
float x = (float) position.getJSONObject(center).getDouble(
x);
//臉部中心點Y坐標
float y = (float) position.getJSONObject(center).getDouble(
y);
//臉部寬度
float width = (float) position.getDouble(width);
//臉部高度
float height = (float) position.getDouble(height);
x = x / 100 * bitmap.getWidth();
y = y / 100 * bitmap.getHeight();
width = width / 100 * bitmap.getWidth();
height = height / 100 * bitmap.getHeight();
//繪制年齡性別的顯示框
mCanvas.drawLine(x - width / 2, y - height / 2, x - width / 2,
y + height / 2, mPaint);
mCanvas.drawLine(x - width / 2, y - height / 2, x + width / 2,
y - height / 2, mPaint);
mCanvas.drawLine(x + width / 2, y - height / 2, x + width / 2,
y + height / 2, mPaint);
mCanvas.drawLine(x - width / 2, y + height / 2, x + width / 2,
y + height / 2, mPaint);
int age = face.getJSONObject(attribute).getJSONObject(age)
.getInt(value);
String gender = face.getJSONObject(attribute)
.getJSONObject(gender).getString(value);
Bitmap ageBitmap = buildAgeBitmap(age, gender.equals(Male));
int ageWidth = ageBitmap.getWidth();
int ageHeight = ageBitmap.getHeight();
//對年齡性別的顯示框大小進行調整
if (bitmap.getWidth() < img.getWidth()
&& bitmap.getHeight() < img.getHeight())
{
float ratio = Math.max(
bitmap.getWidth() * 1.0f / img.getWidth(),
bitmap.getHeight() * 1.0f / img.getHeight());
ageBitmap = Bitmap.createScaledBitmap(ageBitmap,
(int) (ageWidth * ratio),
(int) (ageHeight * ratio), false);
}
mCanvas.drawBitmap(ageBitmap, x - ageBitmap.getWidth() / 2, y
- height / 2 - ageBitmap.getHeight(), null);
mPhotoImg = bitmap;
}
} catch (JSONException e)
{
e.printStackTrace();
}
}
在parseRusult方法中, 我們解析從服務器中返回的JSON數據,然後獲取到我們想要的年齡和性別,臉部位置等數據。
用於服務器返回的臉部中心坐標和寬高等數據是使用在圖片中的百分比所表示的,所以我們需要做下面的處理將之轉換成真實像素位置。
//臉部中心點X坐標
float x = (float) position.getJSONObject(center).getDouble(
x);
//臉部中心點Y坐標
float y = (float) position.getJSONObject(center).getDouble(
y);
//臉部寬度
float width = (float) position.getDouble(width);
//臉部高度
float height = (float) position.getDouble(height);
x = x / 100 * bitmap.getWidth();
y = y / 100 * bitmap.getHeight();
width = width / 100 * bitmap.getWidth();
height = height / 100 * bitmap.getHeight();
然後 ,我們繪制臉部的識別框,就是示例圖中的那個紅色方框。他們是四條線段繪制的。
//繪制年齡性別的顯示框
mCanvas.drawLine(x - width / 2, y - height / 2, x - width / 2,
y + height / 2, mPaint);
mCanvas.drawLine(x - width / 2, y - height / 2, x + width / 2,
y - height / 2, mPaint);
mCanvas.drawLine(x + width / 2, y - height / 2, x + width / 2,
y + height / 2, mPaint);
mCanvas.drawLine(x - width / 2, y + height / 2, x + width / 2,
y + height / 2, mPaint);
下一步,我們還需要將 表示性別和年齡的顯示框繪制在相應的人臉框的上邊,並對顯示框做相應的校正,防止其過大。
int age = face.getJSONObject(attribute).getJSONObject(age)
.getInt(value);
String gender = face.getJSONObject(attribute)
.getJSONObject(gender).getString(value);
//年齡顯示框的圖像
Bitmap ageBitmap = buildAgeBitmap(age, gender.equals(Male));
int ageWidth = ageBitmap.getWidth();
int ageHeight = ageBitmap.getHeight();
//對年齡性別的顯示框大小進行調整
if (bitmap.getWidth() < img.getWidth()
&& bitmap.getHeight() < img.getHeight())
{
float ratio = Math.max(
bitmap.getWidth() * 1.0f / img.getWidth(),
bitmap.getHeight() * 1.0f / img.getHeight());
ageBitmap = Bitmap.createScaledBitmap(ageBitmap,
(int) (ageWidth * ratio),
(int) (ageHeight * ratio), false);
}
mCanvas.drawBitmap(ageBitmap, x - ageBitmap.getWidth() / 2, y
- height / 2 - ageBitmap.getHeight(), null);
mPhotoImg = bitmap;
}
} catch (JSONException e)
{
e.printStackTrace();
}
}
我們的性別年齡顯示框其實就是一個TextView ,並在左邊通過drawableLeft設置了表示性別的圖片。buildAgeBitmap函數用於將TextView轉換為Bitmap對象
//繪制年齡性別顯示框,將TextView轉換為Bitmap對象
private Bitmap buildAgeBitmap(int age, boolean isMale)
{
ageAndgender = (TextView) getLayoutInflater().inflate(
R.layout.age_layout, null);
ageAndgender.setText(age + );
if (isMale)
{
ageAndgender.setCompoundDrawablesWithIntrinsicBounds(getResources()
.getDrawable(R.drawable.male), null, null, null);
} else
{
ageAndgender.setCompoundDrawablesWithIntrinsicBounds(getResources()
.getDrawable(R.drawable.female), null, null, null);
}
ageAndgender.setDrawingCacheEnabled(true);
ageAndgender.measure(
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
ageAndgender.layout(0, 0, ageAndgender.getMeasuredWidth(),
ageAndgender.getMeasuredHeight());
ageAndgender.buildDrawingCache();
Bitmap bitmap = Bitmap.createBitmap(ageAndgender.getDrawingCache());
return bitmap;
}
其中age_layout.xml就是性別年齡顯示框的布局文件。
age_layout.xml
最後,我們需要對底部的拍照、相冊選擇圖片和識別按鈕進行處理。
@Override
public void onClick(View v)
{
switch (v.getId())
{
//打開相冊 選取圖片
case R.id.open_photo:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_PICK);
intent.setType(image/*);
startActivityForResult(intent, PICK_CODE);
break;
//照相並獲取圖片
case R.id.open_camera:
//圖片名稱 時間命名
SimpleDateFormat format = new SimpleDateFormat(yyyyMMddHHmmss);
Date date = new Date(System.currentTimeMillis());
filename = format.format(date);
//創建File對象用於存儲拍照的圖片 SD卡根目錄
File path = Environment.getExternalStorageDirectory();
File outputImage = new File(path,filename+.jpg);
try {
if(outputImage.exists()) {
outputImage.delete();
}
outputImage.createNewFile();
} catch(IOException e) {
e.printStackTrace();
}
//將File對象轉換為Uri並啟動照相程序
imageUri = Uri.fromFile(outputImage);
Intent cameras = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); //照相
cameras.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); //指定圖片輸出地址
startActivityForResult(cameras,TAKE_PICTURE); //啟動照相
//拍完照startActivityForResult() 結果返回onActivityResult()函數
break;
//開始識別
case R.id.detect:
if (mCurrentPhotoPath != null
&& !mCurrentPhotoPath.trim().equals())
{
resizePhoto();
} else if(!isCamera)
{
//重置默認圖片
mPhotoImg = BitmapFactory.decodeResource(getResources(),
R.drawable.level1);
}
dialog.show();
//對圖片進行識別
FaceRecognize.recognize(mPhotoImg, new CallBack()
{
@Override
public void success(JSONObject result)
{
Message msg = Message.obtain();
msg.what = MSG_SUCCESS;
msg.obj = result;
mHandler.sendMessageDelayed(msg, 500);
}
@Override
public void error(FaceppParseException e)
{
Message msg = Message.obtain();
msg.what = MSG_ERROR;
msg.obj = e.getErrorMessage();
mHandler.sendMessageDelayed(msg, 500);
}
});
break;
}
}
在OnActivityResult回調方法中,我們分別處理拍照、相冊選擇照片和圖片裁剪等操作。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
switch (requestCode)
{
//從相冊中選擇相片
case PICK_CODE:
if (data != null)
{
Uri uri = data.getData();
Cursor cursor = getContentResolver().query(uri, null, null,
null, null);
cursor.moveToFirst();
int index = cursor
.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
mCurrentPhotoPath = cursor.getString(index);
cursor.close();
// 壓縮照片
resizePhoto();
img.setImageBitmap(mPhotoImg);
tip.setText(Detect-->);
}
break;
//照相
case TAKE_PICTURE:
if (resultCode == RESULT_OK)
{
//我們需要對圖片進行剪裁
Intent intent = new Intent(com.android.camera.action.CROP); //剪裁
intent.setDataAndType(imageUri, image/*);
intent.putExtra(scale, true);
//設置寬高比例
intent.putExtra(aspectX, 1);
intent.putExtra(aspectY, 1);
//設置裁剪圖片寬高
intent.putExtra(outputX, 340);
intent.putExtra(outputY, 340);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
Toast.makeText(MainActivity.this, 剪裁圖片, Toast.LENGTH_SHORT).show();
//廣播刷新相冊
Intent intentBc = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intentBc.setData(imageUri);
this.sendBroadcast(intentBc);
startActivityForResult(intent, CROP_PHOTO); //設置裁剪參數顯示圖片至ImageView
}
break;
//剪裁圖片
case CROP_PHOTO:
//圖片解析成Bitmap對象
try
{
//Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), imageUri);
mPhotoImg = BitmapFactory.decodeStream(
getContentResolver().openInputStream(imageUri));
Toast.makeText(MainActivity.this, imageUri.toString(), Toast.LENGTH_SHORT).show();
isCamera=true;
img.setImageBitmap(mPhotoImg);
tip.setText(Detect-->); //將剪裁後照片顯示出來
} catch (FileNotFoundException e)
{
e.printStackTrace();
}
break;
}
super.onActivityResult(requestCode, resultCode, data);
}
注意,從相冊中選擇的圖片,我們需要重新調整其大小,防止其尺寸過大而使得程序崩潰。resizeBitmap方法用於調整圖片大小
//重置圖片的大小 對圖片進行壓縮
private void resizePhoto()
{
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(mCurrentPhotoPath, options);
double scaleRatio = Math.max(options.outWidth * 1.0d / 1024f,
options.outHeight * 1.0d / 1024f);
options.inSampleSize = (int) Math.ceil(scaleRatio);
options.inJustDecodeBounds = false;
mPhotoImg = BitmapFactory.decodeFile(mCurrentPhotoPath, options);
}
最後 ,對我們的程序進行測試~
想必大家都用過QQ的白板功能,裡面主要有兩項,一個是塗鴉功能,其實類似於上節的畫板功能,而另一個就是手寫,那記事本怎麼能沒有這個功能呢,今天就來為我們的記事本添加手寫功能
Android WebView常見問題解決方案匯總:就目前而言,如何應對版本的頻繁更新呢,又如何靈活多變地展示我們的界面呢,這又涉及到了web app與native ap
效果圖源碼KqwOpenCVBlurDemo阈值化是一種將我們想要在圖像中分析的區域分割出來的方法。我們把每個像素值都與一個預設的阈值做比較,再根據比較的結果調整像素值。
前言 時光飛逝,從事Android系統開發已經兩年了,總想寫點什麼來安慰自己。思考了很久總是無法下筆,覺得沒什麼好寫的。現在終於決定寫一些符合大多數人需求的東西,想必使用