OpenCV 2.4.3.2を使用してカメラアプリを作成し、opencv処理を実行しようとしています。私は、Landscapeだけでなく、複数のUI方向を持つことができるようにしたいと思います。
問題は、向きを縦向きに変更すると、画像が横向きになることです。
入力画像を回転させるだけでよい 画像処理を行う前に(したがって、向きのみを横向きのままにしておきます)、これは問題なく動作しますが、残りの問題は解決しません。 UIの向きが間違っています。
また、 このコード を使用してカメラを90度回転させようとしましたが、動作しないようです。
mCamera.setDisplayOrientation(90);
効果がないか、場合によってはプレビューが黒く表示されるだけです。
OpenCVでこれを成功させた人はいますか?私のクラスはJavaCameraViewから拡張されています。
改善を行いました。これは、CameraBridgeViewBase.Javaクラスに表示されるOpenCV内の画像を回転させたことです。
配信および描画フレーム方式では:
if (canvas != null) {
canvas.drawColor(0, Android.graphics.PorterDuff.Mode.CLEAR);
//canvas.drawBitmap(mCacheBitmap, (canvas.getWidth() - mCacheBitmap.getWidth()) / 2, (canvas.getHeight() - mCacheBitmap.getHeight()) / 2, null);
//Change to support portrait view
Matrix matrix = new Matrix();
matrix.preTranslate((canvas.getWidth() - mCacheBitmap.getWidth()) / 2,(canvas.getHeight() - mCacheBitmap.getHeight()) / 2);
if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT)
matrix.postRotate(90f,(canvas.getWidth()) / 2,(canvas.getHeight()) / 2);
canvas.drawBitmap(mCacheBitmap, matrix, new Paint());
...基本的に、これは入力画像を次のように回転させるだけです。
これは良いですが、私は明らかにこれをフルスクリーンにしたいです。
OpenCVを実装しようとしても同じ問題がありました。 deliverAndDrawFrameメソッドに次の変更を加えることで修正できました。
キャンバスオブジェクトを回転させる
Canvas canvas = getHolder().lockCanvas();
// Rotate canvas to 90 degrees
canvas.rotate(90f, canvas.getWidth()/2, canvas.getHeight()/2);
描画する前に、キャンバスのサイズ全体に合うようにビットマップのサイズを変更します
// Resize
Bitmap bitmap = Bitmap.createScaledBitmap(mCacheBitmap, canvas.getHeight(), canvas.getWidth(), true);
// Use bitmap instead of mCacheBitmap
canvas.drawBitmap(bitmap, new Rect(0,0,bitmap.getWidth(), bitmap.getHeight()), new Rect(
(int)((canvas.getWidth() - mScale*bitmap.getWidth()) / 2),
(int)((canvas.getHeight() - mScale*bitmap.getHeight()) / 2),
(int)((canvas.getWidth() - mScale*bitmap.getWidth()) / 2 + mScale*bitmap.getWidth()),
(int)((canvas.getHeight() - mScale*bitmap.getHeight()) / 2 + mScale*bitmap.getHeight()
)), null);
// Unlock canvas
getHolder().unlockCanvasAndPost(canvas);
実際には、幅または高さを数学の親(フルスクリーン)にすることができます。
if (canvas != null) {
Bitmap bitmap = Bitmap.createScaledBitmap(mCacheBitmap, canvas.getHeight(), canvas.getWidth(), true);
canvas.rotate(90,0,0);
float scale = canvas.getWidth() / (float)bitmap.getHeight();
float scale2 = canvas.getHeight() / (float)bitmap.getWidth();
if(scale2 > scale){
scale = scale2;
}
if (scale != 0) {
canvas.scale(scale, scale,0,0);
}
canvas.drawBitmap(bitmap, 0, -bitmap.getHeight(), null);
...
また、プレビューサイズを画面より大きくすることもできます。スケールを変更するだけです。
CameraBridgeViewBase.Javaを次のように変更しました。
protected Size calculateCameraFrameSize(List<?> supportedSizes, ListItemAccessor accessor, int surfaceWidth, int surfaceHeight) {
int calcWidth = 0;
int calcHeight = 0;
if(surfaceHeight > surfaceWidth){
int temp = surfaceHeight;
surfaceHeight = surfaceWidth;
surfaceWidth = temp;
}
そして、関数「deliverAndDrawFrame」で:
if (mScale != 0) {
if(canvas.getWidth() > canvas.getHeight()) {
canvas.drawBitmap(mCacheBitmap, new Rect(0,0,mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),
new Rect((int)((canvas.getWidth() - mScale*mCacheBitmap.getWidth()) / 2),
(int)((canvas.getHeight() - mScale*mCacheBitmap.getHeight()) / 2),
(int)((canvas.getWidth() - mScale*mCacheBitmap.getWidth()) / 2 + mScale*mCacheBitmap.getWidth()),
(int)((canvas.getHeight() - mScale*mCacheBitmap.getHeight()) / 2 + mScale*mCacheBitmap.getHeight())), null);
} else {
canvas.drawBitmap(mCacheBitmap, rotateMe(canvas, mCacheBitmap), null);
}
ここで、rotateMeは次のように定義されます。
private Matrix rotateMe(Canvas canvas, Bitmap bm) {
// TODO Auto-generated method stub
Matrix mtx=new Matrix();
float scale = (float) canvas.getWidth() / (float) bm.getHeight();
mtx.preTranslate((canvas.getWidth() - bm.getWidth())/2, (canvas.getHeight() - bm.getHeight())/2);
mtx.postRotate(90,canvas.getWidth()/2, canvas.getHeight()/2);
mtx.postScale(scale, scale, canvas.getWidth()/2 , canvas.getHeight()/2 );
return mtx;
}
横モードと比較した場合の計算オーバーヘッドのため、プレビューFPSは遅くなります。
残念ながら、Opencv4Androidはポートレートカメラをサポートしていません。しかし、それを克服する方法があります。 1)カスタムカメラを作成し、向きを縦に設定します。 2)プレビューコールバックに登録します。 3)onPreviewFrame(byte[]data, Camera camera)
create Mat
のプレビューバイト:
Mat mat = new Mat(previewSize.height, previewSize.width, CvType.CV_8UC1);
mat.put(0, 0, data);
Core.transpose(mat, mat);
Core.flip(mat, mat, -1); // rotates Mat to portrait
CvType
は、カメラが使用しているプレビュー形式によって異なります。
PS。完了したら、作成したすべてのMatインスタンスをリリースすることを忘れないでください。
PPS。検出中にUIスレッドを過負荷にしないために、カメラを別のスレッドで管理することをお勧めします。
私は同じ問題を抱えています、私はそれを理解していました!!私の解決策があります:
最初の一部として、_CameraBridgeViewBase.Java
_、2つのコンストラクターで、WindowManagerの初期化を追加します。
_public CameraBridgeViewBase(Context context, int cameraId) {
super(context);
mCameraIndex = cameraId;
getHolder().addCallback(this);
mMaxWidth = MAX_UNSPECIFIED;
mMaxHeight = MAX_UNSPECIFIED;
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
_
}
_public CameraBridgeViewBase(Context context, AttributeSet attrs) {
super(context, attrs);
int count = attrs.getAttributeCount();
Log.d(TAG, "Attr count: " + Integer.valueOf(count));
TypedArray styledAttrs = getContext().obtainStyledAttributes(attrs, R.styleable.CameraBridgeViewBase);
if (styledAttrs.getBoolean(R.styleable.CameraBridgeViewBase_show_fps, false))
enableFpsMeter();
mCameraIndex = styledAttrs.getInt(R.styleable.CameraBridgeViewBase_camera_id, -1);
getHolder().addCallback(this);
mMaxWidth = MAX_UNSPECIFIED;
mMaxHeight = MAX_UNSPECIFIED;
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
styledAttrs.recycle();
_
}
次に、次のように関数deliverAndDrawFrame(CvCameraViewFrame frame)
を置き換える必要があります。
_protected void deliverAndDrawFrame(CvCameraViewFrame frame) {
Mat modified;
if (mListener != null) {
modified = mListener.onCameraFrame(frame);
} else {
modified = frame.rgba();
}
boolean bmpValid = true;
if (modified != null) {
try {
Utils.matToBitmap(modified, mCacheBitmap);
} catch (Exception e) {
Log.e(TAG, "Mat type: " + modified);
Log.e(TAG, "Bitmap type: " + mCacheBitmap.getWidth() + "*" + mCacheBitmap.getHeight());
Log.e(TAG, "Utils.matToBitmap() throws an exception: " + e.getMessage());
bmpValid = false;
}
}
if (bmpValid && mCacheBitmap != null) {
Canvas canvas = getHolder().lockCanvas();
if (canvas != null) {
canvas.drawColor(0, Android.graphics.PorterDuff.Mode.CLEAR);
int rotation = windowManager.getDefaultDisplay().getRotation();
int degrees = 0;
// config degrees as you need
switch (rotation) {
case Surface.ROTATION_0:
degrees = 90;
break;
case Surface.ROTATION_90:
degrees = 0;
break;
case Surface.ROTATION_180:
degrees = 270;
break;
case Surface.ROTATION_270:
degrees = 180;
break;
}
Matrix matrix = new Matrix();
matrix.postRotate(degrees);
Bitmap outputBitmap = Bitmap.createBitmap(mCacheBitmap, 0, 0, mCacheBitmap.getWidth(), mCacheBitmap.getHeight(), matrix, true);
if (outputBitmap.getWidth() <= canvas.getWidth()) {
mScale = getRatio(outputBitmap.getWidth(), outputBitmap.getHeight(), canvas.getWidth(), canvas.getHeight());
} else {
mScale = getRatio(canvas.getWidth(), canvas.getHeight(), outputBitmap.getWidth(), outputBitmap.getHeight());
}
if (mScale != 0) {
canvas.scale(mScale, mScale, 0, 0);
}
Log.d(TAG, "mStretch value: " + mScale);
canvas.drawBitmap(outputBitmap, 0, 0, null);
if (mFpsMeter != null) {
mFpsMeter.measure();
mFpsMeter.draw(canvas, 20, 30);
}
getHolder().unlockCanvasAndPost(canvas);
}
}
_
}
この機能を追加して、
_private float getRatio(int widthSource, int heightSource, int widthTarget, int heightTarget) {
if (widthTarget <= heightTarget) {
return (float) heightTarget / (float) heightSource;
} else {
return (float) widthTarget / (float) widthSource;
}
_
}
それはオーケーであり、この答えがあなたに役立つ場合は、「受け入れられた」ヘルプレピュテーションをマークしてください
ここでの答えはすべてハックです。私はこの解決策を好む:
javaCameraViewコードの変更:
mBuffer = new byte[size];
mCamera.setDisplayOrientation(90); //add this
mCamera.addCallbackBuffer(mBuffer);
2番目の変更:
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
// mSurfaceTexture = new SurfaceTexture(MAGIC_TEXTURE_ID);
// mCamera.setPreviewTexture(mSurfaceTexture);
// } else
// mCamera.setPreviewDisplay(null);
mCamera.setPreviewDisplay(getHolder());
OpenCV 2.4.9を使用している場合は、1)コード内のopencvチュートリアル混合処理の内容をコピーしてみてください。 2)不一致エラー(アクティビティ名とおそらくレイアウト参照)を修正します。 3)Android:screenOrientation ="landscape"
を追加してマニフェストを変更します4)未成年者のエラーを修正して実行します!!!! bbaamm(今は正しく動作するはずです)
注:この方法では、電話が縦位置にあるときに右側にステータスバーが表示されます。カメラプロジェクトを開発しているため、プレビューからステータスバーを削除することをお勧めします。
それが役に立てば幸い !!!
新しいOpenCV _CameraBridgeViewBase.Java
_クラスは高レベルであり、カメラプレビューのレイアウトを十分に制御できないようです。 サンプルコード を見てください。これは、古いOpenCVサンプルの一部に基づいており、純粋なAndroidコードを使用しています。onPreviewFrame
、put()
をMatに変換し、YUVからRGBに変換します。
_mYuv = new Mat(previewHeight + previewHeight/2, previewWidth, CvType.CV_8UC1);
mYuv.put(0, 0, mBuffer);
Imgproc.cvtColor(mYuv, mRgba, Imgproc.COLOR_YUV420sp2RGBA, 4);
_
古いOpenCV4Androidのサンプルはインターネット上で見つけることができるかもしれませんが、それらは数バージョン前に削除されました。ただし、リンクされたサンプルコードと上記のスニペットで十分です。
@Kaye Wrobleskiの回答に感謝します。横向きと縦向きの両方を許可するように拡張しました。これは基本的に、横向きを与えるデフォルトのコードとポートレート用の彼のコードを簡単に切り替えられるようにするためのほんの少しの追加コードです。
CameraBridgeViewBase.Javaに新しいメソッドとしてコードを挿入します
protected void deliverAndDrawFramePortrait(CvCameraViewFrame frame) {
Mat modified;
if (mListener != null) {
modified = mListener.onCameraFrame(frame);
} else {
modified = frame.rgba();
}
boolean bmpValid = true;
if (modified != null) {
try {
Utils.matToBitmap(modified, mCacheBitmap);
} catch(Exception e) {
Log.e(TAG, "Mat type: " + modified);
Log.e(TAG, "Bitmap type: " + mCacheBitmap.getWidth() + "*" + mCacheBitmap.getHeight());
Log.e(TAG, "Utils.matToBitmap() throws an exception: " + e.getMessage());
bmpValid = false;
}
}
if (bmpValid && mCacheBitmap != null) {
Canvas canvas = getHolder().lockCanvas();
// Rotate canvas to 90 degrees
canvas.rotate(90f, canvas.getWidth()/2, canvas.getHeight()/2);
if (canvas != null) {
canvas.drawColor(0, Android.graphics.PorterDuff.Mode.CLEAR);
Log.d(TAG, "mStretch value: " + mScale);
if (mScale != 0) {
// Resize
Bitmap bitmap = Bitmap.createScaledBitmap(mCacheBitmap, canvas.getHeight(), canvas.getWidth(), true);
// Use bitmap instead of mCacheBitmap
canvas.drawBitmap(bitmap, new Rect(0,0,bitmap.getWidth(), bitmap.getHeight()), new Rect(
(int)((canvas.getWidth() - mScale*bitmap.getWidth()) / 2),
(int)((canvas.getHeight() - mScale*bitmap.getHeight()) / 2),
(int)((canvas.getWidth() - mScale*bitmap.getWidth()) / 2 + mScale*bitmap.getWidth()),
(int)((canvas.getHeight() - mScale*bitmap.getHeight()) / 2 + mScale*bitmap.getHeight())), null);
} else {
Bitmap bitmap = Bitmap.createScaledBitmap(mCacheBitmap, canvas.getHeight(), canvas.getWidth(), true);
// Use bitmap instead of mCacheBitmap
canvas.drawBitmap(bitmap, new Rect(0,0,bitmap.getWidth(), bitmap.getHeight()), new Rect(
(int)((canvas.getWidth() - bitmap.getWidth()) / 2),
(int)((canvas.getHeight() - bitmap.getHeight()) / 2),
(int)((canvas.getWidth() - bitmap.getWidth()) / 2 + bitmap.getWidth()),
(int)((canvas.getHeight() - bitmap.getHeight()) / 2 + bitmap.getHeight())), null);
}
if (mFpsMeter != null) {
mFpsMeter.measure();
mFpsMeter.draw(canvas, 20, 30);
}
getHolder().unlockCanvasAndPost(canvas);
}
}
}
次に、JavaCameraView.Javaを変更します
ポートレートモードかランドスケープモードかを追跡する新しい変数を追加します
private boolean portraitMode;
2つのメソッドを追加して、方向モードを設定します
public void setLandscapeMode() {
portraitMode = false;
}
public void setPortraitMode() {
portraitMode = true;
}
JavaCameraView CameraWorkerClass、run()メソッドでこれらの行を置き換えます
if (!mFrameChain[1 - mChainIdx].empty())
deliverAndDrawFrame(mCameraFrame[1 - mChainIdx]);
これらの行で:
if (!mFrameChain[1 - mChainIdx].empty()) {
if (!portraitMode) {
deliverAndDrawFrame(mCameraFrame[1 - mChainIdx]);
} else {
deliverAndDrawFramePortrait(mCameraFrame[1 - mChainIdx]);
}
}
方向を切り替えるには、JavaCameraViewオブジェクトでsetLandscapeMode()またはsetPortraitMode()を呼び出すだけです。
逆向きのポートレートと逆向きのランドスケープの向きは逆さまのままであることに注意してください。それらを正しい向きにするには180度回転する必要がありますが、これはOpenCVのwarpAffine()メソッドで簡単に行えます。バックカメラ(LENS_FACING_BACK)を使用する場合、ポートレートモードでは画像が上下逆になります。
いくつかのことを考慮する必要があります。
そのため、高速かつ高解像度のソリューションのために、JavaCameraView.Javaと私のJNIパートを変更しました。 JavaCameraView.Javaの場合:
...
if (sizes != null) {
/* Select the size that fits surface considering maximum size allowed */
Size frameSize;
if(width > height)
{
frameSize = calculateCameraFrameSize(sizes, new JavaCameraSizeAccessor(), width, height);
}else{
frameSize = calculateCameraFrameSize(sizes, new JavaCameraSizeAccessor(), height, width);
}
...
mCamera.setParameters(params);
params = mCamera.getParameters();
int bufFrameWidth, bufFrameHeight;
bufFrameWidth = params.getPreviewSize().width;
bufFrameHeight = params.getPreviewSize().height;
if(width > height) {
mFrameWidth = params.getPreviewSize().width;
mFrameHeight = params.getPreviewSize().height;
}else{
mFrameWidth = params.getPreviewSize().height;
mFrameHeight = params.getPreviewSize().width;
}
...
mFrameChain = new Mat[2];
mFrameChain[0] = new Mat(bufFrameHeight + (bufFrameHeight/2), bufFrameWidth, CvType.CV_8UC1);
mFrameChain[1] = new Mat(bufFrameHeight + (bufFrameHeight/2), bufFrameWidth, CvType.CV_8UC1);
AllocateCache();
mCameraFrame = new JavaCameraFrame[2];
mCameraFrame[0] = new JavaCameraFrame(mFrameChain[0], bufFrameWidth, bufFrameHeight);
mCameraFrame[1] = new JavaCameraFrame(mFrameChain[1], bufFrameWidth, bufFrameHeight);
これらの変更により、ポートレートで使用可能な最高の結果を使用するようにしました(calculateCameraFrameSizeで高さ/幅を切り替えます)。 onPreviewFrame()からの入力としてランドスケープを処理していますが、ビットマップを作成してポートレート(AllocateCache)で描画します。
最後に、「立っている」オブジェクトを検出し、ビットマップの保存とレンダリングのために戻すために、アルゴリズムにポートレートフレームを与える必要があります。したがって、アクティビティに次の変更を加えます。
public Mat rot90(Mat matImage, int rotflag){
//1=CW, 2=CCW, 3=180
Mat rotated = new Mat();
if (rotflag == 1){
rotated = matImage.t();
flip(rotated, rotated, 1); //transpose+flip(1)=CW
} else if (rotflag == 2) {
rotated = matImage.t();
flip(rotated, rotated,0); //transpose+flip(0)=CCW
} else if (rotflag ==3){
flip(matImage, rotated,-1); //flip(-1)=180
} else if (rotflag != 0){ //if not 0,1,2,3:
Log.e(TAG, "Unknown rotation flag("+rotflag+")");
}
return rotated;
}
public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
mRgba = rot90(inputFrame.rgba(), 1);
mGray = rot90(inputFrame.gray(), 1);
...
「jaiprakashgogi」開発者の答えは私のために働いています。しかし、問題はプレビューがまだ風景としてのみ保存されていることです。つまり、プレビューをimageviewに設定すると、風景として表示されます。
上記のソリューションは、プレビューをポートレートとして表示するまで機能しますが、ポートレートとして永続的に保存されません。
私はその問題を次のように解決しました。
ここに私のコードを見てください...
public String writeToSDFile(byte[] data, int rotation){
byte[] portraitData=null;
if(rotation==90){
Log.i(TAG,"Rotation is : "+rotation);
Bitmap bitmap= BitmapFactory.decodeByteArray(data,0,data.length);
Matrix matrix = new Matrix();
matrix.postRotate(90);
Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap , 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
portraitData=bitmapToByte(rotatedBitmap);
}
File dir=getDirectory();
String imageTime=""+System.currentTimeMillis();
String fileName=Constants.FILE_NAME+imageTime+"."+Constants.IMAGE_FORMAT;
File file = new File(dir, fileName);
try {
FileOutputStream f = new FileOutputStream(file);
if(rotation==90){
f.write(portraitData);
}else {
f.write(data);
}
f.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
Log.i(TAG, "******* File not found. Did you" +
" add a WRITE_EXTERNAL_STORAGE permission to the manifest?");
} catch (IOException e) {
e.printStackTrace();
}
Log.i(TAG,"\n\nFile written to "+file);
return fileName;
}
// convert bitmap to Byte Array
public byte[] bitmapToByte(Bitmap bitmap){
ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG,100,outputStream);
byte[] array=outputStream.toByteArray();
return array;
}
それは私の問題を完全に解決します。
CameraBridgeViewBaseで縦向きになりましたが、OpenCV内でJavaCameraView.Javaを変更する必要がありました:(アイデアは次:カメラの初期化後、次を実行
setDisplayOrientation(mCamera, 90);
mCamera.setPreviewDisplay(getHolder());
およびsetDisplayOrientationメソッド
protected void setDisplayOrientation(Camera camera, int angle){
Method downPolymorphic;
try
{
downPolymorphic = camera.getClass().getMethod("setDisplayOrientation", new Class[] { int.class });
if (downPolymorphic != null)
downPolymorphic.invoke(camera, new Object[] { angle });
}
catch (Exception e1)
{
}
}
他の回答と同様に、deliverAndDrawFrameの個人用バージョンを書きました(通知もあります)私のコードが開始および終了するコメントを通じて)
protected void deliverAndDrawFrame(CvCameraViewFrame frame) {
Mat modified;
if (mListener != null) {
modified = mListener.onCameraFrame(frame);
} else {
modified = frame.rgba();
}
boolean bmpValid = true;
if (modified != null) {
try {
Utils.matToBitmap(modified, mCacheBitmap);
} catch(Exception e) {
Log.e(TAG, "Mat type: " + modified);
Log.e(TAG, "Bitmap type: " + mCacheBitmap.getWidth() + "*" + mCacheBitmap.getHeight());
Log.e(TAG, "Utils.matToBitmap() throws an exception: " + e.getMessage());
bmpValid = false;
}
}
if (bmpValid && mCacheBitmap != null) {
Canvas canvas = getHolder().lockCanvas();
if (canvas != null) {
canvas.drawColor(0, Android.graphics.PorterDuff.Mode.CLEAR);
if (BuildConfig.DEBUG) {
Log.d(TAG, "mStretch value: " + mScale);
}
// Start of the fix
Matrix matrix = new Matrix();
matrix.preTranslate( ( canvas.getWidth() - mCacheBitmap.getWidth() ) / 2f, ( canvas.getHeight() - mCacheBitmap.getHeight() ) / 2f );
matrix.postRotate( 90f, ( canvas.getWidth()) / 2f, canvas.getHeight() / 2f );
float scale = (float) canvas.getWidth() / (float) mCacheBitmap.getHeight();
matrix.postScale(scale, scale, canvas.getWidth() / 2f , canvas.getHeight() / 2f );
canvas.drawBitmap( mCacheBitmap, matrix, null );
// Back to original OpenCV code
if (mFpsMeter != null) {
mFpsMeter.measure();
mFpsMeter.draw(canvas, 20, 30);
}
getHolder().unlockCanvasAndPost(canvas);
}
}
}
次のように、プレビューがポートレートモードになりました。