Практика преобразования формата изображения в приложении для распознавания лиц

Android

В настоящее время API ArcFace 2.0 поддерживает несколько форматов изображений:BGR24,NV21,NV12,I420,YUYV(Android и IOS поддерживают только некоторые из них). Далее мы представим эти форматы изображений и некоторые методы преобразования.

1. Введение в соответствующее цветовое пространство изображения

1. Цветовое пространство RGB

RGB颜色空间Основываясь на трех основных цветах: красном, зеленом и синем, выполняются различные степени наложения для получения насыщенных и обширных цветов, поэтому этот режим широко известен как режим трех основных цветов. Общие форматы RGB:RGB_565,RGB_888,ARGB_8888,ARGB_4444Ждать.

2. Цветовое пространство YUV

существуетYUV颜色空间Среди них Y используется для представления яркости, а U и V используются для представления цветности. Общие форматы YUV имеют следующие категории:planar:Y, U, V сохраняются постоянно, напримерI420,YV12 packed:Y, U, V перекрестное хранение, напримерYUYV semi-planar:Y хранится непрерывно, а U и V хранятся перекрестно, напримерNV21,NV12

2. Введение в связанные форматы изображений

1. Формат изображения BGR24

BGR24Формат изображения — это формат, использующий 24 бита на пиксель (бит на пиксель). Каждый цветовой канал B, G, R занимает по 8 бит на пикс. Аранжировано как:

B G R  B G R  B G R  B G R  B G R  B G R  B G R  B G R 

B G R  B G R  B G R  B G R  B G R  B G R  B G R  B G R 

B G R  B G R  B G R  B G R  B G R  B G R  B G R  B G R 

B G R  B G R  B G R  B G R  B G R  B G R  B G R  B G R 

2. Формат изображения NV21

NV21Формат изображения принадлежитYUVв цветовом пространствеYUV420SPФормат, каждые четыре компонента Y имеют общий набор компонентов U и компонентов V, Y сортируется последовательно, а U и V перекрестно сортируются. Аранжировано как:

Y Y  Y Y  Y Y  Y Y
Y Y  Y Y  Y Y  Y Y

Y Y  Y Y  Y Y  Y Y
Y Y  Y Y  Y Y  Y Y

V U  V U  V U  V U

V U  V U  V U  V U

3. Формат изображения NV12

NV12Формат изображения принадлежитYUVв цветовом пространствеYUV420SPФормат, каждые четыре компонента Y совместно используют набор компонентов U и компонентов V, Y сортируется последовательно, а U и V перекрестно сортируются (NV12иNV21Просто позиции U и V поменялись местами). Аранжировано как:

Y Y  Y Y  Y Y  Y Y
Y Y  Y Y  Y Y  Y Y

Y Y  Y Y  Y Y  Y Y
Y Y  Y Y  Y Y  Y Y

U V  U V  U V  U V

U V  U V  U V  U V

4. Формат изображения I420

I420Формат изображения принадлежитYUVв цветовом пространствеYUV420PФормат, каждые четыре компонента Y имеют общий набор компонентов U и компонентов V, а Y, U и V расположены последовательно. (Чтобы объяснить общую связь между Y, U и V, ни U, ни V не переносят строку) Аранжировано как:

Y Y  Y Y  Y Y  Y Y
Y Y  Y Y  Y Y  Y Y

Y Y  Y Y  Y Y  Y Y
Y Y  Y Y  Y Y  Y Y

U  U  U  U  U  U  U  U 
V  V  V  V  V  V  V  V 

5. Формат изображения YV12

YV12Формат изображения принадлежитYUVв цветовом пространствеYUV420PФормат: каждые четыре компонента Y совместно используют набор компонентов U и компонентов V, а Y, U и V упорядочены последовательно (чтобы объяснить общую связь между Y, U и V, U и V не переносятся строками). ) (YV12иI420Просто позиции U и V поменялись местами). Аранжировано как:

Y Y  Y Y  Y Y  Y Y
Y Y  Y Y  Y Y  Y Y

Y Y  Y Y  Y Y  Y Y
Y Y  Y Y  Y Y  Y Y

V  V  V  V  V  V  V  V 
U  U  U  U  U  U  U  U 

6. Формат изображения YUYV

YUYVФормат изображения принадлежитYUVв цветовом пространствеYUV422Format каждые два компонента Y совместно используют набор компонентов U и компонентов V, а Y, U и V перекрестно отсортированы. Аранжировано как:

Y U Y V   Y U Y V  Y U Y V  Y U Y V

Y U Y V   Y U Y V  Y U Y V  Y U Y V

Y U Y V   Y U Y V  Y U Y V  Y U Y V

Y U Y V   Y U Y V  Y U Y V  Y U Y V

3. Преобразование формата изображения

Из-за разнообразия форматов изображений существует бесчисленное множество методов преобразования, если вы понимаетеYUVиRGBБлагодаря тому, как организованы данные, самостоятельное написание кода преобразования изображений не занимает много времени. Код Java части преобразования изображения, указанный ниже, приведен для справки.

1. ИзBitmapполучено вARGB_8888Данные формата изображения (платформа Android)

BitmapПоддерживается несколько форматов:ALPHA_8,RGB_565,ARGB_4444,ARGB_8888,RGBA_F16,HARDWARE. Наш текущий основной выборARGB_8888Преобразование форматов. мы можем использоватьBitmapв классеpublic void getPixels(@ColorInt int[] pixels, int offset, int stride, int x, int y, int width, int height)способ получитьint[]argb данные типа илиpublic void copyPixelsToBuffer (Buffer dst)способ получитьbyte[]ТипARGB_8888данные.

2. ARGB_8888преобразовать вBGR_24

Например, для изображения 4x2,ARGB_8888Содержание формата:

A1 R1 G1 B1  A2 R2 G2 B2  A3 R3 G3 B3  A4 R4 G4 B4
A5 R5 G5 B5  A6 R6 G6 B6  A7 R7 G7 B7  A8 R8 G8 B8

Затем, если вам нужно преобразовать вBGR_24, содержимое станет:

B1 G1 R1  B2 G2 R2  B3 G3 R3  B4 G4 R4
B5 G5 R5  B6 G6 R6  B7 G7 R7  B8 G8 R8

BGR_24Содержание 3byteОдна группа,ARGB_8888Содержание 4byteОдна группа. Поэтому для первой группыARGB_8888(A1 R1 G1 B1)и первая группаBGR_24(B1 G1 R1), и соответствующее отношение:

bgr24[0] = argb8888[3];
bgr24[1] = argb8888[2];
bgr24[2] = argb8888[1];

Соответствующий код преобразования:

    public static byte[] argb8888ToBgr24(byte[] argb8888) {
        if (argb8888 == null){
            throw new IllegalArgumentException("invalid image params!");
        }
        int groupNum = argb8888.length / 4;
        byte[] bgr24 = new byte[groupNum * 3];
        int bgr24Index = 0;
        int argb8888Index = 0;
        for (int i = 0; i < groupNum; i++) {
            bgr24[bgr24Index] = argb8888[argb8888Index + 2];
            bgr24[bgr24Index + 1] = argb8888[argb8888Index + 1];
            bgr24[bgr24Index + 2] = argb8888[argb8888Index];
            bgr24Index += 3;
            argb8888Index += 4;
        }
        return bgr24;
    }

3. ARGB_8888преобразовать вNV21

rgbизменятьyuvАлгоритм:

int y = (66 * r + 129 * g + 25 * b + 128 >> 8) + 16;
int u = (-38 * r - 74 * g + 112 * b + 128 >> 8) + 128;
int v = (112 * r - 94 * g - 18 * b + 128 >> 8) + 128;

Метод преобразования:

  • int[]ТипARGB_8888данные преобразуются вNV21:
    private static byte[] argbToNv21(int[] argb, int width, int height) {
        if (argb == null || argb.length == 0 || width * height != argb.length) {
            throw new IllegalArgumentException("invalid image params!");
        }
        int yIndex = 0;
        int uvIndex = width * height;
        int argbIndex = 0;
        byte[] nv21 = new byte[width * height * 3 / 2];
        for (int j = 0; j < height; ++j) {
            for (int i = 0; i < width; ++i) {
                //对于int型color数据,格式为0xAARRGGBB,可进行与运算后移位取对应A R G B,
                //但是该YUV转换公式中不需要ALPHA,因此我们只需要取 R G B 即可。
                int r = (argb[argbIndex] & 0xFF0000) >> 16;
                int g = (argb[argbIndex] & 0x00FF00) >> 8;
                int b = argb[argbIndex] & 0x0000FF;
                //获取该像素点的R G B,并转换为Y U V,但byte范围是0x00~0xFF,因此在赋值时还需进行判断
                int y = (66 * r + 129 * g + 25 * b + 128 >> 8) + 16;
                nv21[yIndex++] = (byte) (y < 0 ? 0 : (y > 0xFF ? 0xFF : y));
                if ((j & 1) == 0 && (argbIndex & 1) == 0 && uvIndex < nv21.length - 2) {
                    int u = (-38 * r - 74 * g + 112 * b + 128 >> 8) + 128;
                    int v = (112 * r - 94 * g - 18 * b + 128 >> 8) + 128;
                    nv21[uvIndex++] = (byte) (v < 0 ? 0 : (v > 0xFF ? 0xFF : v));
                    nv21[uvIndex++] = (byte) (u < 0 ? 0 : (u > 0xFF ? 0xFF : u));
                }
                ++argbIndex;
            }
        }
        return nv21;
    }
  • byte[]ТипARGB_8888данные преобразуются вNV21(Принцип тот же, что и в способе 1):
   private static byte[] argbToNv21(byte[] argb, int width, int height) {
        if (argb == null || argb.length == 0 || width * height * 4 != argb.length) {
            throw new IllegalArgumentException("invalid image params!");
        }
        int yIndex = 0;
        int uvIndex = width * height;
        int argbIndex = 0;
        byte[] nv21 = new byte[width * height * 3 / 2];
        for (int j = 0; j < height; ++j) {
            for (int i = 0; i < width; ++i) {
                argbIndex++;
                int r = argb[argbIndex++];
                int g = argb[argbIndex++];
                int b = argb[argbIndex++];
                r &= 0x000000FF;
                g &= 0x000000FF;
                b &= 0x000000FF;
                int y = ((66 * r + 129 * g + 25 * b + 128 >> 8) + 16);
                nv21[yIndex++] = (byte) (y > 0xFF ? 0xFF : (y < 0 ? 0 : y));
                if ((j & 1) == 0 && ((argbIndex >> 2) & 1) == 0 && uvIndex < nv21.length - 2) {
                    int u = ((-38 * r - 74 * g + 112 * b + 128 >> 8) + 128);
                    int v = ((112 * r - 94 * g - 18 * b + 128 >> 8) + 128);
                    nv21[uvIndex++] = (byte) (v > 0xFF ? 0xFF : (v < 0 ? 0 : v));
                    nv21[uvIndex++] = (byte) (u > 0xFF ? 0xFF : (u < 0 ? 0 : u));
                }
            }
        }
        return nv21;
    }

4. NV21преобразовать вBGR24

yuvизменятьrgbалгоритм:

int r = (int) ((y & 0xFF) + 1.4075 * ((v & 0xFF) - 128));
int g = (int) ((y & 0xFF) - 0.3455 * ((u & 0xFF) - 128) - 0.7169 * ((v & 0xFF) - 128));
int b = (int) ((y & 0xFF) + 1.779 * ((u & 0xFF) - 128));

Метод преобразования:

    private static byte[] nv21ToBgr24(byte[] nv21, int width, int height) {
        if (nv21 == null || nv21.length == 0 || width * height * 3 / 2 != nv21.length) {
            throw new IllegalArgumentException("invalid image params!");
        }
        byte[] bgr24 = new byte[width * height * 3];
        int bgrLineSize = width * 3;
        //偶数行的bgr数据下标
        int evenLineBgrIndex = 0;
        //奇数行的bgr数据下标
        int oddLineBgrIndex = bgrLineSize;
        //当前一行y数据最左边的下标
        int yLineStart = 0;
        //uv数据的下标
        int uvIndex = width * height;
        //由于NV21的共用关系,每2行做一次转换
        for (int i = 0; i < height; i += 2) {
            for (int widthOffset = 0; widthOffset < width; widthOffset++) {
                byte v = nv21[uvIndex];
                byte u = nv21[uvIndex + 1];
                byte yEven = nv21[yLineStart + widthOffset];
                byte yOdd = nv21[yLineStart + width + widthOffset];
                //偶数行YUV转RGB
                int r, g, b;
                r = (int) ((yEven & 0xFF) + 1.4075 * ((v & 0xFF) - 128));
                g = (int) ((yEven & 0xFF) - 0.3455 * ((u & 0xFF) - 128) - 0.7169 * ((v & 0xFF) - 128));
                b = (int) ((yEven & 0xFF) + 1.779 * ((u & 0xFF) - 128));
                r = r < 0 ? 0 : r > 0xFF ? 0xFF : r;
                g = g < 0 ? 0 : g > 0xFF ? 0xFF : g;
                b = b < 0 ? 0 : b > 0xFF ? 0xFF : b;
                bgr24[evenLineBgrIndex++] = (byte) b;
                bgr24[evenLineBgrIndex++] = (byte) g;
                bgr24[evenLineBgrIndex++] = (byte) r;
                //奇数行YUV转RGB
                r = (int) ((yOdd & 0xFF) + 1.4075 * ((v & 0xFF) - 128));
                g = (int) ((yOdd & 0xFF) - 0.3455 * ((u & 0xFF) - 128) - 0.7169 * ((v & 0xFF) - 128));
                b = (int) ((yOdd & 0xFF) + 1.779 * ((u & 0xFF) - 128));
                r = r < 0 ? 0 : r > 0xFF ? 0xFF : r;
                g = g < 0 ? 0 : g > 0xFF ? 0xFF : g;
                b = b < 0 ? 0 : b > 0xFF ? 0xFF : b;
                bgr24[oddLineBgrIndex++] = (byte) b;
                bgr24[oddLineBgrIndex++] = (byte) g;
                bgr24[oddLineBgrIndex++] = (byte) r;
                //每两个y将uv下标增1
                if ((widthOffset & 1) == 1) {
                    uvIndex += 2;
                }
            }
            //由于在内层循环中已经做过width * 3次自增,所以外层循环中只需要增加一行
            evenLineBgrIndex += bgrLineSize;
            oddLineBgrIndex += bgrLineSize;
            //y增2行
            yLineStart += width * 2;
        }
        return bgr24;
    }

5. NV12иNV21менять

NV21иNV12Просто расположение данных U и V отличается, поэтомуNV21преобразовать вNV12Код также работает дляNV12преобразовать вNV21. Вы можете обратиться к следующему коду:

public static byte[] nv21ToNv12(byte[] nv21, int width, int height) {
        if (nv21 == null || nv21.length == 0 || width * height * 3 / 2 != nv21.length) {
            throw new IllegalArgumentException("invalid image params!");
        }
        final int ySize = width * height;
        int totalSize = width * height * 3 / 2;

        byte[] nv12 = new byte[nv21.length];
        //复制Y
        System.arraycopy(nv21, 0, nv12, 0, ySize);
        //UV互换
        for (int uvIndex = ySize; uvIndex < totalSize; uvIndex += 2) {
            nv12[uvIndex] = nv21[uvIndex + 1];
            nv12[uvIndex + 1] = nv21[uvIndex];
        }
        return nv12;
    }

6. NV21изменятьYV12

NV21превратиться вYV12Процесс в основном заключается в изменении перекрестной сортировки своих УФ-данных на непрерывную сортировку. Вы можете обратиться к следующему коду:

public static byte[] nv21ToYv12(byte[] nv21, int width, int height) {
        if (nv21 == null || nv21.length == 0 || width * height * 3 / 2 != nv21.length) {
            throw new IllegalArgumentException("invalid image params!");
        }
        final int ySize = width * height;
        int totalSize = width * height * 3 / 2;
        byte[] yv12 = new byte[nv21.length];
        int yv12UIndex = ySize;
        int yv12VIndex = ySize * 5 / 4;
        //复制Y
        System.arraycopy(nv21, 0, yv12, 0, ySize);
        //复制UV
        for (int uvIndex = ySize; uvIndex < totalSize; uvIndex += 2) {
            yv12[yv12UIndex++] = nv21[uvIndex];
            yv12[yv12VIndex++] = nv21[uvIndex + 1];
        }
        return yv12;
    }

7. YUYVизменятьNV12

существуетYUYVформат, дваYподелиться группойUиVNV12четыреYподелиться группойUиV, так что этоYUV422изменятьYUV420процесс, нужно отказаться от половиныUиV. Вы можете обратиться к следующему коду:


    public static byte[] yuyvToNv12(byte[] yuyv, int width, int height) {
        if (yuyv == null || yuyv.length == 0) {
            throw new IllegalArgumentException("invalid image params!");
        }
        int ySize = yuyv.length / 2;
        byte[] nv12 = new byte[yuyv.length * 3 / 4];
        int nv12YIndex = 0;
        int nv12UVIndex = ySize;
        boolean copyUV = false;
        int lineDataSize = width * 2;
        for (int i = 0, yuyvIndex = 0; i < height; i++, yuyvIndex += lineDataSize) {
            if (copyUV) {
                for (int lineOffset = 0; lineOffset < lineDataSize; lineOffset += 4) {
                    //复制Y
                    nv12[nv12YIndex++] = yuyv[yuyvIndex + lineOffset];
                    nv12[nv12YIndex++] = yuyv[yuyvIndex + lineOffset + 2];
                    //复制UV
                    nv12[nv12UVIndex++] = yuyv[yuyvIndex + lineOffset + 1];
                    nv12[nv12UVIndex++] = yuyv[yuyvIndex + lineOffset + 3];
                }
            } else {
                for (int lineOffset = 0; lineOffset < lineDataSize; lineOffset += 4) {
                    //复制Y
                    nv12[nv12YIndex++] = yuyv[yuyvIndex + lineOffset];
                    nv12[nv12YIndex++] = yuyv[yuyvIndex + lineOffset + 2];
                }
            }
            copyUV = !copyUV;
        }
        return nv12;
    }

8. I420иYV12менять

I420иYV12ТолькоUиVРасположение данных другое, поэтомуI420преобразовать вYV12Код также работает дляYV12преобразовать вI420. Вы можете обратиться к следующему коду:

public static byte[] i420ToYv12(byte[] i420) {
        if (i420 == null || i420.length == 0 || i420.length % 6 != 0) {
            throw new IllegalArgumentException("invalid image params!");
        }
        int ySize = i420.length * 2 / 3;
        int uvSize = i420.length / 6;
        byte[] yv12 = new byte[i420.length];
        //复制Y
        System.arraycopy(i420, 0, yv12, 0, ySize);
        //UV互换
        System.arraycopy(i420, ySize, yv12, ySize + uvSize, uvSize);
        System.arraycopy(i420, ySize + uvSize, yv12, ySize, uvSize);
        return yv12;
    }

9. I420преобразовать вYUYV

I420иYUYVпо сравнению с,I420изUиVТолькоYUYVполовина, что являетсяYUV420изменятьYUV422процесс, дефектные данные могут быть повторно использованы толькоUиVмакияж, мириться.

public static byte[] i420ToYuyv(byte[] i420, int width, int height) {
        if (i420 == null || i420.length == 0 || i420.length != width * height * 3 / 2) {
            throw new IllegalArgumentException("invalid image params!");
        }
        byte[] yuyv = new byte[width * height * 2];
        int yuyvLineSize = width * 2;
        int i420YIndex = 0;
        int i420UIndex = width * height;
        int i420VIndex = width * height * 5 / 4;
        int yuyvLineStart = 0;
        for (int i = 0; i < height; i += 2) {
            for (int lineOffset = 0; lineOffset < yuyvLineSize; lineOffset += 4) {
                byte u = i420[i420UIndex++];
                byte v = i420[i420VIndex++];
                //偶数行数据赋值
                int yuyvOffset = yuyvLineStart + lineOffset;
                yuyv[yuyvOffset] = i420[i420YIndex];
                yuyv[yuyvOffset + 1] = u;
                yuyv[yuyvOffset + 2] = i420[i420YIndex + 1];
                yuyv[yuyvOffset + 3] = v;
                //奇数行数据赋值
                int yuyvNextLineOffset = yuyvLineStart + yuyvLineSize + lineOffset;
                yuyv[yuyvNextLineOffset] = i420[i420YIndex + width];
                yuyv[yuyvNextLineOffset + 1] = u;
                yuyv[yuyvNextLineOffset + 2] = i420[i420YIndex + width + 1];
                yuyv[yuyvNextLineOffset + 3] = v;

                i420YIndex += 2;
            }
            i420YIndex += width;
            yuyvLineStart += (width << 2);
        }
        return yuyv;
    }

4. Обрезка изображения

Как и в случае преобразования формата, обрезка изображения не представляет сложности, если вы знаете, как расположены изображения. В этой статьеRGBОбрезка изображения в цветовом пространстве иYUVОбрезка изображения в цветовом пространстве.

1. Кроп НВ21

public static byte[] i420ToYv12(byte[] i420) {
        if (i420 == null || i420.length == 0 || i420.length % 6 != 0) {
            throw new IllegalArgumentException("invalid image params!");
        }
        int ySize = i420.length * 2 / 3;
        int uvSize = i420.length / 6;
        byte[] yv12 = new byte[i420.length];
        //复制Y
        System.arraycopy(i420, 0, yv12, 0, ySize);
        //UV互换
        System.arraycopy(i420, ySize, yv12, ySize + uvSize, uvSize);
        System.arraycopy(i420, ySize + uvSize, yv12, ySize, uvSize);
        return yv12;
    }

9. I420преобразовать вYUYV

I420иYUYVпо сравнению с,I420изUиVТолькоYUYVполовина, что являетсяYUV420изменятьYUV422процесс, дефектные данные могут быть повторно использованы толькоUиVмакияж, мириться.

public static byte[] i420ToYuyv(byte[] i420, int width, int height) {
        if (i420 == null || i420.length == 0 || i420.length != width * height * 3 / 2) {
            throw new IllegalArgumentException("invalid image params!");
        }
        byte[] yuyv = new byte[width * height * 2];
        int yuyvLineSize = width * 2;
        int i420YIndex = 0;
        int i420UIndex = width * height;
        int i420VIndex = width * height * 5 / 4;
        int yuyvLineStart = 0;
        for (int i = 0; i < height; i += 2) {
            for (int lineOffset = 0; lineOffset < yuyvLineSize; lineOffset += 4) {
                byte u = i420[i420UIndex++];
                byte v = i420[i420VIndex++];
                //偶数行数据赋值
                int yuyvOffset = yuyvLineStart + lineOffset;
                yuyv[yuyvOffset] = i420[i420YIndex];
                yuyv[yuyvOffset + 1] = u;
                yuyv[yuyvOffset + 2] = i420[i420YIndex + 1];
                yuyv[yuyvOffset + 3] = v;
                //奇数行数据赋值
                int yuyvNextLineOffset = yuyvLineStart + yuyvLineSize + lineOffset;
                yuyv[yuyvNextLineOffset] = i420[i420YIndex + width];
                yuyv[yuyvNextLineOffset + 1] = u;
                yuyv[yuyvNextLineOffset + 2] = i420[i420YIndex + width + 1];
                yuyv[yuyvNextLineOffset + 3] = v;

                i420YIndex += 2;
            }
            i420YIndex += width;
            yuyvLineStart += (width << 2);
        }
        return yuyv;
    }

4. Обрезка изображения

Как и в случае преобразования формата, обрезка изображения не представляет сложности, если вы знаете, как расположены изображения. В этой статьеRGBОбрезка изображения в цветовом пространстве иYUVОбрезка изображения в цветовом пространстве.

1. Обрезать NV21 или NV12

public static byte[] cropYuv420sp(byte[] yuv420sp, int width, int height, int left, int top, int right, int bottom) {
        if (yuv420sp == null || yuv420sp.length == 0 || width * height * 3 / 2 != yuv420sp.length) {
            throw new IllegalArgumentException("invalid image params!");
        }
        if (left < 0 || top < 0 || right > width || bottom > height) {
            throw new IllegalArgumentException("rect out of bounds!");
        }
        if (right < left || bottom < top) {
            throw new IllegalArgumentException("invalid rect!");
        }
        if (((right - left) & 1) == 1 || ((bottom - top) & 1) == 1) {
            throw new IllegalArgumentException("yuv420sp width and height must be even!");
        }
        if ((left & 1 )== 1){
            throw new IllegalArgumentException("yuv420sp crop left borderIndex and right borderIndex must be even!");
        }
        int cropImageWidth = right - left;
        int cropImageHeight = bottom - top;
        byte[] cropYuv420sp = new byte[cropImageWidth * cropImageHeight * 3 / 2];

        //复制Y
        int originalYLineStart = top * width;
        int targetYIndex = 0;

        //复制UV
        int originalUVLineStart = width * height + top * width / 2;
        int targetUVIndex = cropImageWidth * cropImageHeight;
        for (int i = top; i < bottom; i++) {
            System.arraycopy(yuv420sp, originalYLineStart + left, cropYuv420sp, targetYIndex, cropImageWidth);
            originalYLineStart += width;
            targetYIndex += cropImageWidth;
            if ((i & 1) == 0) {
                System.arraycopy(yuv420sp, originalUVLineStart + left, cropYuv420sp, targetUVIndex, cropImageWidth);
                originalUVLineStart += width;
                targetUVIndex += cropImageWidth;
            }
        }
        return cropYuv420sp;
    }

2. Обрезать BGR24

public static byte[] cropBgr24(byte[] bgr24, int width, int height, int left, int top, int right, int bottom) {
        if (bgr24 == null || bgr24.length == 0 || width * height * 3 != bgr24.length) {
            throw new IllegalArgumentException("invalid image params!");
        }
        if (left < 0 || top < 0 || right > width || bottom > height) {
            throw new IllegalArgumentException("rect out of bounds!");
        }
        if (right < left || bottom < top) {
            throw new IllegalArgumentException("invalid rect!");
        }
        int cropImageWidth = right - left;
        int cropImageHeight = bottom - top;
        byte[] cropBgr24 = new byte[cropImageWidth * cropImageHeight * 3];

        int originalLineStart = top * width * 3;
        int targetIndex = 0;
        for (int i = top; i < bottom; i++) {
            System.arraycopy(bgr24, originalLineStart + left * 3, cropBgr24, targetIndex, cropImageWidth * 3);
            originalLineStart += width * 3;
            targetIndex += cropImageWidth * 3;
        }
        return cropBgr24;
    }