0%

opencv3

简单介绍

opencv是用来做原始图片,视频帧级别处理加工,若缩放,裁剪,混合,叠加,灰度以及更高级如AI视觉算法支持等相关功能的代码库,提供各种操作接口。

编译和安装

编译和安装:

  1. 编译ffmpeg:https://linuxize.com/post/how-to-install-ffmpeg-on-ubuntu-20-04/
  2. 编译安装opencv:https://blog.csdn.net/ai_faker/article/details/117020252

    编译工程:

    cmake: 用sample/cpp/example_cmake/下的工程: cmake . & make
    g++ :g++ xxx.cpp -o xxx pkg-config --cflags --libs opencv

或者:

遵循官网的操作:
https://docs.opencv.org/4.5.2/df/d65/tutorial_table_of_content_introduction.html

YUV:

1 YUV格式:

  • 是一种颜色编码方法。常使用在各个视频处理组件中。 YUV在对照片或视频编码时,考虑到人类的感知能力,允许降低色度的带宽。
  • YUV是编译true-color颜色空间(color space)的种类,Y’UV, YUV, YCbCr,YPbPr等专有名词都可以称为YUV,彼此有重叠。“Y”表示明亮度(Luminance或Luma),也就是灰阶值,“U”和“V”表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。

分为两种排列方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1) planar
Y先排布,再排列U,再排列V
如YUV420P:
Y00 Y01 Y02 Y03
Y10 Y11 .........
....
Y30 Y31 .......Y33
U00 U01
U10 U11
V00 V01
V10 V11


2) packed:
yuv 444 : 每个Y对应一组UV分量
Y00 U00 V00 Y01U01V01 Y02U02V02 Y03U03V03
Y10 U10 V10 .....

yuv 422:每2个Y共用一组UV分量
Y00U00 Y01V00 Y02U01 Y03V01
Y10U10 Y11V10..

yuv 420:UV交错 每4个Y共用一组UV分量
Y00U00 Y01 Y02U01Y03 只U 420
Y10V10 Y11 Y12V11Y13 只V420

opencv使用

基于opencv3-4.15

基本例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <opencv2/opencv.hpp>
using namespace cv;
int main(int argc, char** argv )
{
if ( argc != 2 )
{
printf("usage: DisplayImage.out <Image_Path>\n");
return -1;
}
Mat image;
image = imread( argv[1], 1 );
if ( !image.data )
{
printf("No image data \n");
return -1;
}
// WINDOW_NORMAL or WINDOW_AUTOSIZE: WINDOW_NORMAL enables you to resize the window, whereas WINDOW_AUTOSIZE adjusts automatically the window size to fit the displayed image (see imshow ), and you cannot change the window size manually.
// NORMAL可以调整大小,AUTOSIZE不支持手动调整大小
//namedWindow("Display Image", WINDOW_AUTOSIZE );
namedWindow("Display Image", WINDOW_NORMAL );//用于创建一个窗口
imshow("Display Image", image);//展示一个图片到第一个参数指定的窗口,如果这个窗口不在,会创建一个默认的AUTOSIZE的窗口
waitKey(0);//用来等待一个按键再退出,这样类似模拟一个死循环,才能让窗口持续展示,进程不退出
return 0;
}

The Core Functionality (core module) - basic building blocks of the library
  1. Mat - The Basic Image Container 创建矩阵:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    #include "opencv2/core.hpp"
    #include "opencv2/imgproc.hpp"
    #include "opencv2/highgui.hpp"
    #include "opencv2/videoio.hpp"
    #include <iostream>

    using namespace cv;
    using namespace std;


    int main()
    {
    cout << "Built with OpenCV " << CV_VERSION << endl;
    Mat M(2,2,CV_8UC3,Scalar(0,0,255));//2*2 matrx, 每个元素都是0,0,255
    cout<< "M= :" << endl << "" << M << endl;

    int sz[3]={2,2,2};
    Mat L(3,sz,CV_8UC(1),Scalar::all(4));//第一个参数是维度,这里是三维,第二个参数是每个维度是多少;
    // cout << "L:" << endl << "" << L << endl; opencv 三维打印不了

    int sz1[2]={2,2};//是矩阵,所以这两个相等
    Mat L2(2,sz,CV_8UC(1),Scalar::all(4));//这里是二维,每个维度是二
    cout << "L2:" << endl << "" << L2 << endl;

    //看看默认值: 输出为1
    Mat default2();
    cout << "default:" << default2 << endl;

    //通过创建矩阵 :这种情况下默认值是0
    Mat L3;
    L3.create(4,4,CV_8UC(2));
    cout << "M " << endl << L3 << endl;

    //左上角到右下角对角线为1,其他都是0
    Mat E = Mat::eye(4,4,CV_64F);
    cout << "E = " << endl << " " << E << endl << endl;
    Mat O = Mat::ones(2, 2, CV_32F);//全1的
    cout << "O = " << endl << " " << O << endl << endl;
    Mat Z = Mat::zeros(3,3, CV_8UC1);//全0的
    cout << "Z = " << endl << " " << Z << endl << endl;
    //对小矩阵,还可以这样处理:
    Mat C = (Mat_<double>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
    cout << "C = " << endl << " " << C << endl << endl;
    C = (Mat_<double>({0, -1, 0, -1, 5, -1, 0, -1, 0})).reshape(3);//和上面一样,只是通过reshape指定
    cout << "C = " << endl << " " << C << endl << endl;

    //通过clone :下面是取出第一行:
    Mat RowClone = C.row(1).clone();
    cout << "RowClone = " << endl << " " << RowClone << endl << endl; //[-1, 5, -1]

    //初始化为随机值,使用randu函数,需要提供范围
    Mat R = Mat(3, 2, CV_8UC3);
    randu(R, Scalar::all(0), Scalar::all(255));
    cout << "R:" << R << endl;
    return 0;
    }

  2. How to scan images, lookup tables and time measurement with OpenCV
    使用时,三种可以遍历mat的方式,用第三种LUT内建优化的,速度最快:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    #include <opencv2/core.hpp>
    #include <opencv2/core/utility.hpp>
    #include "opencv2/imgcodecs.hpp"
    #include <opencv2/highgui.hpp>
    #include <iostream>
    #include <sstream>

    using namespace std;
    using namespace cv;

    static void help()
    {
    cout
    << "\n--------------------------------------------------------------------------" << endl
    << "This program shows how to scan image objects in OpenCV (cv::Mat). As use case"
    << " we take an input image and divide the native color palette (255) with the " << endl
    << "input. Shows C operator[] method, iterators and at function for on-the-fly item address calculation."<< endl
    << "Usage:" << endl
    << "./how_to_scan_images <imageNameToUse> <divideWith> [G]" << endl
    << "if you add a G parameter the image is processed in gray scale" << endl
    << "--------------------------------------------------------------------------" << endl
    << endl;
    }

    Mat& ScanImageAndReduceC(Mat& I, const uchar* table);
    Mat& ScanImageAndReduceIterator(Mat& I, const uchar* table);
    Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar * table);

    int main( int argc, char* argv[])
    {
    help();
    if (argc < 3)
    {
    cout << "Not enough parameters" << endl;
    return -1;
    }

    Mat I, J;
    if( argc == 4 && !strcmp(argv[3],"G") )
    I = imread(argv[1], IMREAD_GRAYSCALE);
    else
    I = imread(argv[1], IMREAD_COLOR);

    if (I.empty())
    {
    cout << "The image" << argv[1] << " could not be loaded." << endl;
    return -1;
    }

    //! [dividewith]
    int divideWith = 0; // convert our input string to number - C++ style
    stringstream s;
    s << argv[2];
    s >> divideWith;
    if (!s || !divideWith)
    {
    cout << "Invalid number entered for dividing. " << endl;
    return -1;
    }

    uchar table[256];
    for (int i = 0; i < 256; ++i)
    table[i] = (uchar)(divideWith * (i/divideWith));
    //! [dividewith]

    const int times = 100;
    double t;

    t = (double)getTickCount();

    for (int i = 0; i < times; ++i)
    {
    cv::Mat clone_i = I.clone();
    J = ScanImageAndReduceC(clone_i, table);
    }

    t = 1000*((double)getTickCount() - t)/getTickFrequency();
    t /= times;

    cout << "Time of reducing with the C operator [] (averaged for "
    << times << " runs): " << t << " milliseconds."<< endl;

    t = (double)getTickCount();

    for (int i = 0; i < times; ++i)
    {
    cv::Mat clone_i = I.clone();
    J = ScanImageAndReduceIterator(clone_i, table);
    }

    t = 1000*((double)getTickCount() - t)/getTickFrequency();
    t /= times;

    cout << "Time of reducing with the iterator (averaged for "
    << times << " runs): " << t << " milliseconds."<< endl;

    t = (double)getTickCount();

    for (int i = 0; i < times; ++i)
    {
    cv::Mat clone_i = I.clone();
    ScanImageAndReduceRandomAccess(clone_i, table);
    }

    t = 1000*((double)getTickCount() - t)/getTickFrequency();
    t /= times;

    cout << "Time of reducing with the on-the-fly address generation - at function (averaged for "
    << times << " runs): " << t << " milliseconds."<< endl;

    //! [table-init]
    Mat lookUpTable(1, 256, CV_8U);
    uchar* p = lookUpTable.ptr();
    for( int i = 0; i < 256; ++i)
    p[i] = table[i];
    //! [table-init]

    t = (double)getTickCount();

    for (int i = 0; i < times; ++i)
    //! [table-use]
    LUT(I, lookUpTable, J);
    //! [table-use]

    t = 1000*((double)getTickCount() - t)/getTickFrequency();
    t /= times;

    cout << "Time of reducing with the LUT function (averaged for "
    << times << " runs): " << t << " milliseconds."<< endl;
    return 0;
    }

    //! [scan-c]
    Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
    {
    // accept only char type matrices
    CV_Assert(I.depth() == CV_8U);

    int channels = I.channels();

    int nRows = I.rows;
    int nCols = I.cols * channels;

    if (I.isContinuous())
    {
    nCols *= nRows;
    nRows = 1;
    }

    int i,j;
    uchar* p;
    for( i = 0; i < nRows; ++i)
    {
    p = I.ptr<uchar>(i);
    for ( j = 0; j < nCols; ++j)
    {
    p[j] = table[p[j]];
    }
    }
    return I;
    }
    //! [scan-c]

    //! [scan-iterator]
    Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
    {
    // accept only char type matrices
    CV_Assert(I.depth() == CV_8U);

    const int channels = I.channels();
    switch(channels)
    {
    case 1:
    {
    MatIterator_<uchar> it, end;
    for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)
    *it = table[*it];
    break;
    }
    case 3:
    {
    MatIterator_<Vec3b> it, end;
    for( it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it)
    {
    (*it)[0] = table[(*it)[0]];
    (*it)[1] = table[(*it)[1]];
    (*it)[2] = table[(*it)[2]];
    }
    }
    }

    return I;
    }
    //! [scan-iterator]

    //! [scan-random]
    Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)
    {
    // accept only char type matrices
    CV_Assert(I.depth() == CV_8U);

    const int channels = I.channels();
    switch(channels)
    {
    case 1:
    {
    for( int i = 0; i < I.rows; ++i)
    for( int j = 0; j < I.cols; ++j )
    I.at<uchar>(i,j) = table[I.at<uchar>(i,j)];
    break;
    }
    case 3:
    {
    Mat_<Vec3b> _I = I;

    for( int i = 0; i < I.rows; ++i)
    for( int j = 0; j < I.cols; ++j )
    {
    _I(i,j)[0] = table[_I(i,j)[0]];
    _I(i,j)[1] = table[_I(i,j)[1]];
    _I(i,j)[2] = table[_I(i,j)[2]];
    }
    I = _I;
    break;
    }
    }

    return I;
    }
    //! [scan-random]

    use: ./how_to_scan_images lena.jpeg 4 [G]

    Time of reducing with the C operator [] (averaged for 100 runs): 0.897661 milliseconds.
    Time of reducing with the iterator (averaged for 100 runs): 2.54602 milliseconds.
    Time of reducing with the on-the-fly address generation - at function (averaged for 100 runs): 2.3317 milliseconds.
    Time of reducing with the LUT function (averaged for 100 runs): 0.171338 milliseconds.




  3. Mask operations on matrices
    如何做掩码操作:使用内建的掩码操作会更快:传入一个矩阵做运算,这里涉及到复杂的滤波,暂时不深究

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    #include <opencv2/imgcodecs.hpp>
    #include <opencv2/highgui.hpp>
    #include <opencv2/imgproc.hpp>
    #include <iostream>

    using namespace std;
    using namespace cv;

    static void help(char* progName)
    {
    cout << endl
    << "This program shows how to filter images with mask: the write it yourself and the"
    << "filter2d way. " << endl
    << "Usage:" << endl
    << progName << " [image_path -- default lena.jpg] [G -- grayscale] " << endl << endl;
    }


    void Sharpen(const Mat& myImage,Mat& Result);

    int main( int argc, char* argv[])
    {
    help(argv[0]);
    const char* filename = argc >=2 ? argv[1] : "lena.jpg";

    Mat src, dst0, dst1;

    if (argc >= 3 && !strcmp("G", argv[2]))
    src = imread( samples::findFile( filename ), IMREAD_GRAYSCALE);
    else
    src = imread( samples::findFile( filename ), IMREAD_COLOR);

    if (src.empty())
    {
    cerr << "Can't open image [" << filename << "]" << endl;
    return EXIT_FAILURE;
    }

    namedWindow("Input", WINDOW_AUTOSIZE);
    namedWindow("Output", WINDOW_AUTOSIZE);
    imshow( "Input", src );
    double t = (double)getTickCount();

    Sharpen( src, dst0 );

    t = ((double)getTickCount() - t)/getTickFrequency();
    cout << "Hand written function time passed in seconds: " << t << endl;

    imshow( "Output", dst0 );
    waitKey();

    //![kern]
    Mat kernel = (Mat_<char>(3,3) << 0, -1, 0,
    -1, 5, -1,
    0, -1, 0);
    //![kern]

    t = (double)getTickCount();

    //![filter2D]
    filter2D( src, dst1, src.depth(), kernel );
    //![filter2D]
    t = ((double)getTickCount() - t)/getTickFrequency();
    cout << "Built-in filter2D time passed in seconds: " << t << endl;

    imshow( "Output", dst1 );

    waitKey();
    return EXIT_SUCCESS;
    }
    //! [basic_method]
    void Sharpen(const Mat& myImage,Mat& Result)
    {
    //! [8_bit]
    CV_Assert(myImage.depth() == CV_8U); // accept only uchar images
    //! [8_bit]

    //! [create_channels]
    const int nChannels = myImage.channels();
    Result.create(myImage.size(),myImage.type());
    //! [create_channels]

    //! [basic_method_loop]
    for(int j = 1 ; j < myImage.rows-1; ++j)//行数遍历
    {
    const uchar* previous = myImage.ptr<uchar>(j - 1);//获取myImage的某一行,c风格
    const uchar* current = myImage.ptr<uchar>(j );
    const uchar* next = myImage.ptr<uchar>(j + 1);

    uchar* output = Result.ptr<uchar>(j);

    for(int i= nChannels;i < nChannels*(myImage.cols-1); ++i)
    {
    *output++ = saturate_cast<uchar>(5*current[i]
    -current[i-nChannels] - current[i+nChannels] - previous[i] - next[i]);
    }
    }
    //! [basic_method_loop]

    //! [borders]
    Result.row(0).setTo(Scalar(0));
    Result.row(Result.rows-1).setTo(Scalar(0));
    Result.col(0).setTo(Scalar(0));
    Result.col(Result.cols-1).setTo(Scalar(0));
    //! [borders]
    }
    //! [basic_method]

  4. Operations with images

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    1) Mat img = imread(filename); 加载一个图片,通过后缀名识别;
    Mat img = imread(filename, IMREAD_GRAYSCALE);//加载图片,只是加载灰度图,即一个channel
    IMREAD_UNCHANGED 其他:https://docs.opencv.org/master/d8/d6a/group__imgcodecs__flags.html#ga61d9b0126a3e57d9277ac48327799c80
    If set, return the loaded image as is (with alpha channel, otherwise it gets cropped). Ignore EXIF orientation.
    2)imwrite(filename, img); 写同图片到文件filename中;
    3) Scalar intensity = img.at<uchar>(y, x); 整型矩阵中,处于(y,x)的元素;
    //对于一个单channel的灰度图,类型是8UC1的,根据x,y获取像素点值:(8UC1:0-255)
    //注意:因为opencv图是表示和矩阵相同的结构,所以,使用相同的转换:0-based row(行) index(or y-coordinate)为第一个参数,0开始的列(or x)为第二个参数;
    //所以上面的img.at<uchar>(y,x)为从0数起来的第y行,第x列,或者使用下面的形式

    Scalar intensity = img.at<uchar>(Point(x, y));
    Vec3b intensity = img.at<Vec3b>(y, x);
    uchar blue = intensity.val[0];
    uchar green = intensity.val[1];
    uchar red = intensity.val[2];
    改变数据:
    Vec3b inte5(3,4,5);
    img.at<Vec3b>(y,x) = inte5;

    Vec3b inte6 = img.at<Vec3b>(y,x);
    cout << "inet6:" << inte6 << endl;

    4)内存管理和引用计数
    Mat是一种保持矩阵/图像特征(行和列数,数据类型等)和指向数据的指针的结构。所以没有什么可以阻止我们有几个Mat对应于相同数据的实例。当Mat的特定实例被破坏时,Mat保留一个引用计数,指示数据是否必须被释放。以下是创建两个矩阵而不复制数据的示例:

    std::vector<Point3f> points;
    // .. fill the array
    Mat pointsMat = Mat(points).reshape(1); //数据内容还是一样,但是打印channels()会变为1
    真正的拷贝克隆 数据,要用:
    可以使用例如cv :: Mat :: copyTo或cv :: Mat :: clone:

    Mat img = imread(“image.jpg”);
    Mat img1 = img.clone();
    5)create:
    Mat::create函数:
    An empty output Mat can be supplied to each function. Each implementation calls Mat::create for a destination matrix. This method allocates data for a matrix if it is empty. If it is not empty and has the correct size and type, the method does nothing. If however, size or type are different from the input arguments, the data is deallocated (and lost) and a new data is allocated. For example:
    一个空的输出Mat可以被用于每个函数,每个实现会调用create来输出矩阵。这个方法会分配数据,如果是空的,或者如果不空且有正确的size和类型,则不做事。然而
    若size 或type不同于输入的参数,就会先析构,在创建新的;例如:
    Mat img = imread("image.jpg");
    Mat sobelx;
    Sobel(img, sobelx, CV_32F, 1, 0);

    6)原子操作:
    有很多方式可以用来定义一个矩阵,如,这里我们可以make a black image from an existing 灰度图img:
    img = Scalar(0);
    选择一个感兴趣的区域:
    Rect r(10, 10, 100, 100);
    Mat smallImg = img(r);
    把图片转换为灰度图:
    Mat img = imread("image.jpg"); // loading a 8UC3 image
    Mat grey;
    cvtColor(img, grey, COLOR_BGR2GRAY);
    Change image type from 8UC1 to 32FC1:
    src.convertTo(dst, CV_32F);

    7)展示图片:
    展示图片是一个很好展示结果的方式,opencv提供了一种方式,如展示一个8U图片
    Mat img = imread("image.jpg");
    namedWindow("image", WINDOW_AUTOSIZE);
    imshow("image", img);
    waitKey();//调用waitKey()开始一个消息传递循环,等待“image”窗口中的按键。32F图像需要转换为8U类型。
    其他例子:
    Mat img = imread("image.jpg");
    Mat grey;
    cvtColor(img, grey, COLOR_BGR2GRAY);
    Mat sobelx;
    Sobel(grey, sobelx, CV_32F, 1, 0);
    double minVal, maxVal;
    minMaxLoc(sobelx, &minVal, &maxVal); //find minimum and maximum intensities
    Mat draw;
    sobelx.convertTo(draw, CV_8U, 255.0/(maxVal - minVal), -minVal * 255.0/(maxVal - minVal));
    namedWindow("image", WINDOW_AUTOSIZE);
    imshow("image", draw);
    waitKey();
    这里cv::namedWindow是没有必要的,因为它是紧接着cv::imshow。然而,它可以用来改变窗口属性或当使用cv::createTrackbar
  5. 将两个图片叠加在一起—线性混合:
    https://www.cnblogs.com/laizhenghong2012/p/11253100.html
    https://docs.opencv.org/master/d5/dc4/tutorial_adding_images.html
    需要理解的是,单纯乘以某个系数,改变像素值的大小,就能往透明走吗? 如果是透明通道呢?

  6. 如何改变一副图片的对比度和亮度:

这部分的解释来自书籍《计算机视觉:算法和应用》

  • 关于图像处理:
    一个广义上的图像处理,是一个函数,能输入一个或多个图像,然后产生一个输出图像;
    图像转换也可以被看做是:
    点的操作(像素转换)
    邻居(area-based)操作;
  • 关于像素转换:
    在这种图像处理转换中,每个输出的像素点的值,仅仅依赖于相关的输入像素值(以及,潜在的,一些全局的收集的信息或参数)
    这种例子包括 rightness and contrast adjustments as well as color correction and transformations.
  • Brightness and contrast adjustments
    Two commonly used point processes are multiplication and addition with a constant:
    两种常用的点处理方法是常数的乘法和加法: g(x)=αf(x)+β
    The parameters α>0 and β are often called the gain and bias parameters; sometimes these parameters are said to control contrast and brightness respectively.
    参数α>0和β常被称为增益和偏置参数;有时这些参数被称为分别控制对比度和亮度。
    可以把f(x)看作源图像像素,把g(x)看作输出图像像素。然后,我们可以更方便地将表达式写成: g(i,j)=α⋅f(i,j)+β 其中I和j表示该像素位于第I行第j列。
    一个例子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    #include "opencv2/imgcodecs.hpp"
    #include "opencv2/highgui.hpp"
    #include <iostream>
    // we're NOT "using namespace std;" here, to avoid collisions between the beta variable and std::beta in c++17
    using std::cin;
    using std::cout;
    using std::endl;
    using namespace cv;
    int main( int argc, char** argv )
    {
    CommandLineParser parser( argc, argv, "{@input | lena.jpg | input image}" );
    Mat image = imread( samples::findFile( parser.get<String>( "@input" ) ) );
    if( image.empty() )
    {
    cout << "Could not open or find the image!\n" << endl;
    cout << "Usage: " << argv[0] << " <Input image>" << endl;
    return -1;
    }
    Mat new_image = Mat::zeros( image.size(), image.type() );
    double alpha = 1.0; /*< Simple contrast control */
    int beta = 0; /*< Simple brightness control */
    cout << " Basic Linear Transforms " << endl;
    cout << "-------------------------" << endl;
    cout << "* Enter the alpha value [1.0-3.0]: "; cin >> alpha;
    cout << "* Enter the beta value [0-100]: "; cin >> beta;
    for( int y = 0; y < image.rows; y++ ) {
    for( int x = 0; x < image.cols; x++ ) {
    for( int c = 0; c < image.channels(); c++ ) {
    new_image.at<Vec3b>(y,x)[c] =
    saturate_cast<uchar>( alpha*image.at<Vec3b>(y,x)[c] + beta );
    }
    }
    }
    imshow("Original Image", image);
    imshow("New Image", new_image);
    waitKey();
    return 0;
    }

    解释:略 https://docs.opencv.org/master/d3/dc1/tutorial_basic_linear_transform.html
    我们可以简单地使用以下命令,而不是使用for循环来访问每个像素:
    image.convertTo(new_image, -1, alpha, beta);
    where cv::Mat::convertTo
    将有效地执行*new_image = a*image + beta*。但是,我们想向您展示如何访问每个像素。在任何情况下,这两种方法都给出相同的结果,但convertTo更优化,工作速度更快。、
    执行:
    Running our code and using α=2.2 and β=50
    $ ./BasicLinearTransforms lena.jpg
    Basic Linear Transforms
    -------------------------
    * Enter the alpha value [1.0-3.0]: 2.2
    * Enter the beta value [0-100]: 50

    在这一段中,我们将把我们所学到的通过调整图像的亮度和对比度来纠正曝光不足的图像付诸实践。我们还将看到另一种校正图像亮度的技术,称为伽马校正
    疑问:为什么提高RBG的值就能提高亮度?图像的亮度怎么定义的?
    https://bj.96weixin.com/tools/rgb
    这个网站可以看到测试:全0为黑色,全255为白色,随着数值增大,颜色越来越浅,越来越明亮;

    亮度和对比度调整:

    如上图,增加(/减少)β值将为每个像素增加(/减去)一个常量值。[0;255]范围将被饱和(即高于(/低于)255(/ 0)的像素值将被固定为255(/ 0))。

    如上图,α参数将改变能级的传播方式。如果α<1,颜色等级将被压缩,结果将是一个低对比度的图像。(如 (1,2,4) (2,4,8) -->a=2 (2,4,8) (3,8,16)两个点的对比度就更明显了;
    注意,这些直方图是使用Gimp软件中的亮度-对比度工具获得的。亮度工具应该是相同的β偏差参数,但对比工具似乎不同的α增益的输出范围似乎是中心与Gimp(正如你可以注意到在前面的直方图)。
    它可以发生,玩β偏差将提高亮度,但在同一时间图像将出现轻微面纱,因为对比度将降低。α增益可以用来减弱这种效果,但由于饱和度的原因,我们将失去一些原始明亮区域的细节。

    上面的调整是线性的,Gamma校正可以通过使用输入值和映射的输出值之间的非线性变换来校正图像的亮度:
    更多:https://docs.opencv.org/master/d3/dc1/tutorial_basic_linear_transform.html


  1. 离散傅里叶变换 :略
  2. 使用XML和YAML文件的文件输入和输出
  3. 如何使用opencv并行多线程化你的代码;
    ref:https://docs.opencv.org/4.5.2/de/d7a/tutorial_table_of_content_core.html

    更多例子:

    源码目录下:
    /opencv-3.4.15/samples/cpp/tutorial_code/core/…

函数简记

基础

  1. add函数:
    https://cppsecrets.com/users/203110310511410511510410011599115495764103109971051084699111109/C00-OpenCV-cvadd.php
    用于将两个矩阵的值相加,并输出:会出现图像叠加的效果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    This is one of the builtin-function provided by an Opencv() which helps in making addition operation between the two elements (matrix/images).
    Syntax :
    Void cv::add( cv::InputArray src1 , cv::InputArray src2 ,cv::OutputArray dst, cv::InputArray mask=cv::noArray() , int dtype=-1 );
    parameters:
    src1 - first input array or scalar.
    src2 - second input array or a scalar.
    dst - output array that has the same size and number of channels as the input array(s);
    The depth is defined by dtype or src1/src2 .
    mask - optional operation mask- 8 -bit single channel array ,that specifies elements of the output array to be changed.
    dtype - optional depth of the output array.
    Generalization :-
    dst[i] = saturate ( src1[i] + src2[i] )
    cv::add() is a simple addition function : It adds all of the element in src1 to the corresponding element in src2 & puts the result in dest.
  2. mul :Opencv中mul会计算两个Mat矩阵对应位的乘积,所以要求参与运算的矩阵A的行列和B的行列数一致。计算结果是跟A或B行列数一致的一个Mat矩阵。

  3. 如何将透明通道结合到yuv/rgb中;https://cloud.tencent.com/developer/article/1650070

  4. resize: 将mat的 size等做变化:

    1
    resize(srcLocMask, small_mask, cv::Size(w / 2, h / 2), 0.0, 0.0, RESIZE_ALGORITHM); 如果第4,5个参数为0,则用第三个,否则按第4,5个缩放;
  5. Rect矩形:

    1
    2
    3
    4
    5
    6
    7
    8
    Rect_(
    _Tp _x,
    _Tp _y,
    _Tp _width,
    _Tp _height
    );
    x,y:左上角的坐标。这是OpenCV中Rect_::x和Rect_::y的默认解释。不过,在你的算法中,你可以从左下角开始计算x和y。
    width,heigth:矩形的宽度和高度。
  6. 下列用法是拷贝构造?

    1
    2
    srcY = cv::Mat(srcH, srcAVFrame->linesize[0],  CV_8UC1, (void*)srcAVFrame->data[0]);
    srcY = srcY(cv::Rect(0, 0, srcAVFrame->width, srcAVFrame->height));
  7. copyto

  8. convertTo: 转换

9): set
10) 通道处理:

1
2
3
4
5
cv::split(rgbaImg, channels); 通道分离
cv::Mat &alphaImg = channels.at(3); 取出通道
alphaImg.convertTo(avDataParamPtr->m_alphaMat, CV_32F, 1.0/255, 0); //转换值,,A通道
avDataParamPtr->m_alphaRate = 1;
cv::cvtColor(rgbaImg, yuvImg, CV_RGBA2YUV_I420);

11 一个例子:
把png图片透明度调高:即更透明:

1
2
3
1) mat rgba 分离出a通道;
2) 将a通道做运算,值调大,集体乘以1.5
3) 将运算后的a通道的mat合入到rgb中,然后iwrite写入到压缩比最大的png,直接写入吧

基本形状函数:

  • ellipse 画椭圆形,可以指定角度,中心,填充,粗细等; Draws a simple or thick elliptic arc or fills an ellipse sector.
  • circle:画一个圆圈,可以指定中心等;
  • fillPoly 指定多个点,会连接成多边形,可以在参数指定填充等;填充由一个或多个多边形包围的区域。
  • rectangle: 绘制一个简单的、粗的或向上填充的矩形。
  • line() 画一条连接两点的线段

一个随机生成的动画:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
/**
* @file Drawing_2.cpp
* @brief Simple sample code
*/

#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <stdio.h>

using namespace cv;

/// Global Variables
const int NUMBER = 100;
const int DELAY = 10;

const int window_width = 900;
const int window_height = 600;
int x_1 = -window_width/2;
int x_2 = window_width*3/2;
int y_1 = -window_width/2;
int y_2 = window_width*3/2;

/// Function headers
static Scalar randomColor( RNG& rng );
int Drawing_Random_Lines( Mat image, char* window_name, RNG rng );
int Drawing_Random_Rectangles( Mat image, char* window_name, RNG rng );
int Drawing_Random_Ellipses( Mat image, char* window_name, RNG rng );
int Drawing_Random_Polylines( Mat image, char* window_name, RNG rng );
int Drawing_Random_Filled_Polygons( Mat image, char* window_name, RNG rng );
int Drawing_Random_Circles( Mat image, char* window_name, RNG rng );
int Displaying_Random_Text( Mat image, char* window_name, RNG rng );
int Displaying_Big_End( Mat image, char* window_name, RNG rng );


/**
* @function main
*/
int main( void )
{
int c;

/// Start creating a window
char window_name[] = "Drawing_2 Tutorial";

/// Also create a random object (RNG)
RNG rng( 0xFFFFFFFF );

/// Initialize a matrix filled with zeros
Mat image = Mat::zeros( window_height, window_width, CV_8UC3 );
/// Show it in a window during DELAY ms
imshow( window_name, image );
waitKey( DELAY );

/// Now, let's draw some lines
c = Drawing_Random_Lines(image, window_name, rng);
if( c != 0 ) return 0;

/// Go on drawing, this time nice rectangles
c = Drawing_Random_Rectangles(image, window_name, rng);
if( c != 0 ) return 0;

/// Draw some ellipses
c = Drawing_Random_Ellipses( image, window_name, rng );
if( c != 0 ) return 0;

/// Now some polylines
c = Drawing_Random_Polylines( image, window_name, rng );
if( c != 0 ) return 0;

/// Draw filled polygons
c = Drawing_Random_Filled_Polygons( image, window_name, rng );
if( c != 0 ) return 0;

/// Draw circles
c = Drawing_Random_Circles( image, window_name, rng );
if( c != 0 ) return 0;

/// Display text in random positions
c = Displaying_Random_Text( image, window_name, rng );
if( c != 0 ) return 0;

/// Displaying the big end!
c = Displaying_Big_End( image, window_name, rng );
if( c != 0 ) return 0;

waitKey(0);
return 0;
}

/// Function definitions

/**
* @function randomColor
* @brief Produces a random color given a random object
*/
static Scalar randomColor( RNG& rng )
{
int icolor = (unsigned) rng;
return Scalar( icolor&255, (icolor>>8)&255, (icolor>>16)&255 );
}


/**
* @function Drawing_Random_Lines
*/
int Drawing_Random_Lines( Mat image, char* window_name, RNG rng )
{
Point pt1, pt2;

for( int i = 0; i < NUMBER; i++ )
{
pt1.x = rng.uniform( x_1, x_2 );
pt1.y = rng.uniform( y_1, y_2 );
pt2.x = rng.uniform( x_1, x_2 );
pt2.y = rng.uniform( y_1, y_2 );

line( image, pt1, pt2, randomColor(rng), rng.uniform(1, 10), 8 );
imshow( window_name, image );
if( waitKey( DELAY ) >= 0 )
{ return -1; }
}

return 0;
}

/**
* @function Drawing_Rectangles
*/
int Drawing_Random_Rectangles( Mat image, char* window_name, RNG rng )
{
Point pt1, pt2;
int lineType = 8;
int thickness = rng.uniform( -3, 10 );

for( int i = 0; i < NUMBER; i++ )
{
pt1.x = rng.uniform( x_1, x_2 );
pt1.y = rng.uniform( y_1, y_2 );
pt2.x = rng.uniform( x_1, x_2 );
pt2.y = rng.uniform( y_1, y_2 );

rectangle( image, pt1, pt2, randomColor(rng), MAX( thickness, -1 ), lineType );

imshow( window_name, image );
if( waitKey( DELAY ) >= 0 )
{ return -1; }
}

return 0;
}

/**
* @function Drawing_Random_Ellipses
*/
int Drawing_Random_Ellipses( Mat image, char* window_name, RNG rng )
{
int lineType = 8;

for ( int i = 0; i < NUMBER; i++ )
{
Point center;
center.x = rng.uniform(x_1, x_2);
center.y = rng.uniform(y_1, y_2);

Size axes;
axes.width = rng.uniform(0, 200);
axes.height = rng.uniform(0, 200);

double angle = rng.uniform(0, 180);

ellipse( image, center, axes, angle, angle - 100, angle + 200,
randomColor(rng), rng.uniform(-1,9), lineType );

imshow( window_name, image );

if( waitKey(DELAY) >= 0 )
{ return -1; }
}

return 0;
}

/**
* @function Drawing_Random_Polylines
*/
int Drawing_Random_Polylines( Mat image, char* window_name, RNG rng )
{
int lineType = 8;

for( int i = 0; i< NUMBER; i++ )
{
Point pt[2][3];
pt[0][0].x = rng.uniform(x_1, x_2);
pt[0][0].y = rng.uniform(y_1, y_2);
pt[0][1].x = rng.uniform(x_1, x_2);
pt[0][1].y = rng.uniform(y_1, y_2);
pt[0][2].x = rng.uniform(x_1, x_2);
pt[0][2].y = rng.uniform(y_1, y_2);
pt[1][0].x = rng.uniform(x_1, x_2);
pt[1][0].y = rng.uniform(y_1, y_2);
pt[1][1].x = rng.uniform(x_1, x_2);
pt[1][1].y = rng.uniform(y_1, y_2);
pt[1][2].x = rng.uniform(x_1, x_2);
pt[1][2].y = rng.uniform(y_1, y_2);

const Point* ppt[2] = {pt[0], pt[1]};
int npt[] = {3, 3};

polylines(image, ppt, npt, 2, true, randomColor(rng), rng.uniform(1,10), lineType);

imshow( window_name, image );
if( waitKey(DELAY) >= 0 )
{ return -1; }
}
return 0;
}

/**
* @function Drawing_Random_Filled_Polygons
*/
int Drawing_Random_Filled_Polygons( Mat image, char* window_name, RNG rng )
{
int lineType = 8;

for ( int i = 0; i < NUMBER; i++ )
{
Point pt[2][3];
pt[0][0].x = rng.uniform(x_1, x_2);
pt[0][0].y = rng.uniform(y_1, y_2);
pt[0][1].x = rng.uniform(x_1, x_2);
pt[0][1].y = rng.uniform(y_1, y_2);
pt[0][2].x = rng.uniform(x_1, x_2);
pt[0][2].y = rng.uniform(y_1, y_2);
pt[1][0].x = rng.uniform(x_1, x_2);
pt[1][0].y = rng.uniform(y_1, y_2);
pt[1][1].x = rng.uniform(x_1, x_2);
pt[1][1].y = rng.uniform(y_1, y_2);
pt[1][2].x = rng.uniform(x_1, x_2);
pt[1][2].y = rng.uniform(y_1, y_2);

const Point* ppt[2] = {pt[0], pt[1]};
int npt[] = {3, 3};

fillPoly( image, ppt, npt, 2, randomColor(rng), lineType );

imshow( window_name, image );
if( waitKey(DELAY) >= 0 )
{ return -1; }
}
return 0;
}

/**
* @function Drawing_Random_Circles
*/
int Drawing_Random_Circles( Mat image, char* window_name, RNG rng )
{
int lineType = 8;

for (int i = 0; i < NUMBER; i++)
{
Point center;
center.x = rng.uniform(x_1, x_2);
center.y = rng.uniform(y_1, y_2);

circle( image, center, rng.uniform(0, 300), randomColor(rng),
rng.uniform(-1, 9), lineType );

imshow( window_name, image );
if( waitKey(DELAY) >= 0 )
{ return -1; }
}

return 0;
}

/**
* @function Displaying_Random_Text
*/
int Displaying_Random_Text( Mat image, char* window_name, RNG rng )
{
int lineType = 8;

for ( int i = 1; i < NUMBER; i++ )
{
Point org;
org.x = rng.uniform(x_1, x_2);
org.y = rng.uniform(y_1, y_2);

putText( image, "Testing text rendering", org, rng.uniform(0,8),
rng.uniform(0,100)*0.05+0.1, randomColor(rng), rng.uniform(1, 10), lineType);

imshow( window_name, image );
if( waitKey(DELAY) >= 0 )
{ return -1; }
}

return 0;
}

/**
* @function Displaying_Big_End
*/
int Displaying_Big_End( Mat image, char* window_name, RNG )
{
Size textsize = getTextSize("OpenCV forever!", FONT_HERSHEY_COMPLEX, 3, 5, 0);
Point org((window_width - textsize.width)/2, (window_height - textsize.height)/2);
int lineType = 8;

Mat image2;

for( int i = 0; i < 255; i += 2 )
{
image2 = image - Scalar::all(i);
putText( image2, "OpenCV forever!", org, FONT_HERSHEY_COMPLEX, 3,
Scalar(i, i, 255), 5, lineType );

imshow( window_name, image2 );
if( waitKey(DELAY) >= 0 )
{ return -1; }
}

return 0;
}

混画操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
s1 : 创建画布: 


//传入空帧,输出宽,输出高,237,128,128 yuv的值
void OpenCVOperator::createYUV(MediaFrame& tMediaFrame, int w, int h, const int& y, const int& u, const int& v)
{
__try__
if(w&1 || h&1)
{
LOG->debug() << __FUNCTION__ << " ERROR w:" << w << " h:" << h << " %2 != 0" << endl;
w = w + (w&1);
h = h + (h&1);
}
//avframe分配和宽高,格式为YUV420P
AVFrame* avFrame = av_frame_alloc();
avFrame->width = w;
avFrame->height = h;
avFrame->format = AV_PIX_FMT_YUV420P; 即先Y再U再V
//avpicture_get_size 算出某格式和分辨率下一帧图像的数据大小
//avpicture_fill 将自己分配的内存绑定到AVFrame帧的data数据区
//avpicture_alloc 为AVFrame帧的data分配内存,不用自己分配
获得yuv帧大小:
int yuvFrameSize = avpicture_get_size(AV_PIX_FMT_YUV420P, w, h);
av_malloc分配一段buffer内存数据;
uint8_t* buffer = (uint8_t*)av_malloc(yuvFrameSize);
avpicture_fill((AVPicture*)avFrame, buffer, AV_PIX_FMT_YUV420P, w, h);

Mat mat = cv::Mat(h + h/2, w, CV_8UC1, (void*)buffer);//这里可以看到buffer其实连续的,Y:h*w,U:h/4*w,V:h/4*w
mat.setTo(y);
cv::Mat uMat(mat(cv::Rect(0, h, w, h/4)));
uMat.setTo(u);
cv::Mat vMat(mat(cv::Rect(0, h*5/4, w, h/4)));
vMat.setTo(v);
//上面是先在对应的YUVbuffer部分上做默认色设置;注意Y,U,V这里的画布是分开的;
tMediaFrame.setAVFrame(avFrame, buffer, yuvFrameSize);
tMediaFrame.asVideo();
tMediaFrame.setWidth(w);
tMediaFrame.setHeight(h);
uint32_t timestamp = TimestampAdjuster::getElapseFromServerStart();
tMediaFrame.setDts(timestamp);
tMediaFrame.setPts(timestamp);
__catch__
return;
}

开始混画:注意,画布对应的YUV分三个,内存连续的buffer,但是mat上的w和h是抽象的矩阵大小;
//输出帧,输入帧,输入帧的摆放点左上角(坐标),输入帧的摆放位置长宽,输入帧的裁剪rect
void OpenCVOperator::addYUV(MediaFrame& dst, MediaFrame& src, cv::Point& point, int w, int h, cv::Rect& rect, const std::vector<cv::Point> clipPolygon, double clipFactor)
{
TimeUse t(__FUNCTION__);

dst.addIdTimestamp(src.getStreamId(), src.getDts());
auto timeTrace = src.getIdTimeTrace();
for (const auto& kv :timeTrace)
{
for (const auto& key_time : kv.second)
{
dst.addIdTimeTrace(src.getStreamId(), key_time.first, key_time.second);
}
}
dst.addIdTimeTrace(src.getStreamId(), TimeTraceKey::MIXED, getNowMs32());

/*
auto seiData = src.getIdSeiData();
for (const auto& kv : seiData)
{
dst.addIdSeiData(src.getStreamId(), kv.second.first, kv.second.second);
}
*/
s2: 对裁剪的,输出的长宽都限制为偶数;
int x = point.x, y = point.y; //point为输入帧json的put_ret的左上角位置

if(rect.x&1 || rect.y &1)//裁剪的左上角坐标值不能为奇数?
{
mixlog(MIXLOG << "error:" << " 截取 坐标有毒 x:" << rect.x << " y:" << rect.y);
rect.x = rect.x - (rect.x&1);
rect.y = rect.y - (rect.y&1);
}

if(rect.width&1 || rect.height&1)//裁剪的宽高不能为奇数?
{
mixlog(MIXLOG << " 截取 宽高有毒 w:" << rect.width << " h:" << rect.height);
rect.width = rect.width - (rect.width&1);
rect.height = rect.height - (rect.height&1);
}

//输入帧的摆放的位置,左上角和右下角的坐标调整为偶数 //往里缩
w = w - (w&1);
h = h - (h&1);
x = x - (x&1);
y = y - (y&1);

//输出的宽高
int dstW = dst.getWidth(), dstH = dst.getHeight(); // 输出 宽高
//源数据的宽高--解码后的AVframe得知
int srcW = src.getWidth(), srcH = src.getHeight();
AVFrame* dstAVFrame = dst.getAVFrame();
AVFrame* srcAVFrame = src.getAVFrame();

s3: 裁剪和输出的不能超过输出画布大小,超过要修正;
// put_rect 判断 摆放位置,宽高要比输出的小
if(!( x >=0 && y >=0 && x+w <= dstW && y+h <= dstH ))
{
mixlog(MIXLOG << "error:" << " ERROR put_rect(" << x << "," << y << "," << w << "," << h
<< "), dstW:" << dstW << ", dstH:" << dstH << " beyond output| adjust");

//按左上角为最大调即,
/*
|-------->x轴
| x,y
|
V*/
x = std::min(dstW, std::max(0, x));//最大不能比dstW大
y = std::min(dstH, std::max(0, y));//最大不能比dstH大,如果都是相等,则大小为0了
w = std::min(dstW-x, w);//最大不能超出dstW范围
h = std::min(dstH-y, h);//最大不能超出dstH范围
}

// crop_rect 判断,最大到输入帧本身的大小
if(!( rect.x >= 0 && rect.y >=0 && rect.x + rect.width <= srcW && rect.y + rect.height <= srcH ))
{
mixlog(MIXLOG << "error" << " ERROR crop_rect(" << rect.x << "," << rect.y << "," << rect.width << "," << rect.height
<< ",) srcW:" << srcW << ", srcH:" << srcH <<" beyond input| adjust");

rect = cv::Rect( 0, 0, srcW, srcH);
}

if (srcAVFrame == NULL || dstAVFrame == NULL)//无输入输出帧,返回
{
mixlog(MIXLOG << "null frame");
return;
}
s4: 设置输入的内容对应到画布上的位置区域: dstLocY,dstLocU,dstLocV,注意这里输出的U,V也是只有Y的1/4,所以xy点也要对应缩小
__try__
uint8_t* dstBuffer = dst.getBuffer(); // 画布
cv::Mat dstY = cv::Mat(dstH, dstW, CV_8UC1, dstBuffer);//第一个第二个参数为高宽,最后一个参数为数据填充的起点
cv::Mat dstU = cv::Mat(dstH/2, dstW/2, CV_8UC1, dstBuffer + dstH * dstW);
cv::Mat dstV = cv::Mat(dstH/2, dstW/2, CV_8UC1, dstBuffer + dstH * dstW + dstH/2 * dstW/2);

cv::Mat dstLocY = dstY( cv::Rect(x, y, w, h) ); // 画布局部区域Y
cv::Mat dstLocU = dstU( cv::Rect(x/2, y/2, w/2, h/2) );// 画布局部区域U
cv::Mat dstLocV = dstV( cv::Rect(x/2, y/2, w/2, h/2) ); // 画布局部区域V

cv::Mat srcY;
cv::Mat srcU;
cv::Mat srcV;
S5:把YUV当成三个MAT处理,绑定源SRC帧的buffer
if(src.hasBuffer())
{
uint8_t* srcBuffer = src.getBuffer();
srcY = cv::Mat(srcH, srcW, CV_8UC1, srcBuffer);
srcU = cv::Mat(srcH/2, srcW/2, CV_8UC1, srcBuffer + srcW * srcH);
srcV = cv::Mat(srcH/2, srcW/2, CV_8UC1, srcBuffer + srcW * srcH + srcW/2 * srcH/2);
}
else // 无buffer
{
srcY = cv::Mat(srcH, srcAVFrame->linesize[0], CV_8UC1, (void*)srcAVFrame->data[0]);
srcU = cv::Mat(srcH/2, srcAVFrame->linesize[1], CV_8UC1, (void*)srcAVFrame->data[1]);
srcV = cv::Mat(srcH/2, srcAVFrame->linesize[2], CV_8UC1, (void*)srcAVFrame->data[2]);

srcY = srcY(cv::Rect(0, 0, srcAVFrame->width, srcAVFrame->height));
srcU = srcU(cv::Rect(0,0, srcAVFrame->width /2, srcAVFrame->height/2));
srcV = srcV(cv::Rect(0,0, srcAVFrame->width /2, srcAVFrame->height/2));
}
S6 进行裁剪 RECT 对输入帧
//裁剪下
cv::Mat srcLocY = srcY(rect);
cv::Mat srcLocU = srcU(cv::Rect(rect.x/2, rect.y/2, rect.width/2, rect.height/2));
cv::Mat srcLocV = srcV(cv::Rect(rect.x/2, rect.y/2, rect.width/2, rect.height/2));

S7 裁剪后缩放,放大或缩小,利用resize函数;
// real mix
cv::Mat formatSrcY, formatSrcU, formatSrcV; //缩放,format为缩放的结果
resize(srcLocY, formatSrcY, cv::Size(w, h), 0.0, 0.0, RESIZE_ALGORITHM);
resize(srcLocU, formatSrcU, cv::Size(w/2, h/2), 0.0, 0.0, RESIZE_ALGORITHM);
resize(srcLocV, formatSrcV, cv::Size(w/2, h/2), 0.0, 0.0, RESIZE_ALGORITHM);

const bool clipPolygonMode = !src.isAlpha() && !clipPolygon.empty();
if(src.isAlpha() || clipPolygonMode)
{
float alphaRate = src.getAlphaRate(); //alpha按比例缩小
cv::Mat srcMask;
if (clipPolygonMode)
srcMask = cv::Mat(h, w, CV_32FC1, alphaRate);
else
srcMask = src.getAlpha() * alphaRate;//只是浮点数相乘,也就是透明度调整
cv::Mat srcLocMask, small_mask;
cv::resize(srcMask(rect), srcLocMask, cv::Size(w, h), 0.0, 0.0, RESIZE_ALGORITHM);//裁剪后,缩放为摆放位置的,结果再srcLocMask中,给y分量

if (!clipPolygon.empty())
{
TimeUse t("clip polygon", 1);

int scale = std::max(1, (int)clipFactor);
std::vector<std::vector<cv::Point>> contours_;
contours_.push_back(clipPolygon);
std::vector<cv::Point>& points = contours_.back();
if (scale > 1)
{
for (auto& point : points)
{
point.x *= scale;
point.y *= scale;

mixlog(MIXLOG << "clip pt.x" << point.x << ", pt.y:" << point.y);
}
}

Mat alpha = cv::Mat(h * scale, w * scale, CV_32FC1, 0.0f);
cv::fillPoly(alpha, contours_, 1.0f, LINE_AA);
if (scale > 1)
cv::resize(alpha, alpha, cv::Size(w, h), 0.0, 0.0, RESIZE_ALGORITHM);

srcLocMask = srcLocMask.mul(alpha);
}

resize(srcLocMask, small_mask, cv::Size(w / 2, h / 2), 0.0, 0.0, RESIZE_ALGORITHM);//给uv分量
//缩放结果和画布局部位置的数据类型都先转换为32
cv::Mat fSrcY, fSrcU, fSrcV, fDstY, fDstU, fDstV;
formatSrcY.convertTo(fSrcY, CV_32FC1);
formatSrcU.convertTo(fSrcU, CV_32FC1);
formatSrcV.convertTo(fSrcV, CV_32FC1);
dstLocY.convertTo(fDstY, CV_32FC1);
dstLocU.convertTo(fDstU, CV_32FC1);
dstLocV.convertTo(fDstV, CV_32FC1);
//做透明分量合入到yuv分量
cv::add(fSrcY.mul(srcLocMask), fDstY.mul(1.0 - srcLocMask), fDstY);
cv::add(fSrcU.mul(small_mask), fDstU.mul(1.0 - small_mask), fDstU);
cv::add(fSrcV.mul(small_mask), fDstV.mul(1.0 - small_mask), fDstV);

if (dst.isTransparentLayer())
{
cv::Mat& fDstA = dst.getAlpha();
mixlog(MIXLOG << "dst is transparentLayer. srcLocMask.w:" << srcLocMask.cols << ", srcLocMask.h:" << srcLocMask.rows <<
", fDstA.w:" << fDstA.cols << ", fDstA.h:" << fDstA.rows);
cv::Mat fDstARange = fDstA(cv::Rect(x, y, w, h));
mixlog(MIXLOG << "fDstARange, w:" << fDstARange.cols << ", h:" << fDstARange.rows);
cv::add(srcLocMask, fDstARange.mul(1.0 - srcLocMask), fDstARange);
}

fDstY.convertTo(dstLocY, CV_8UC1);
fDstU.convertTo(dstLocU, CV_8UC1);
fDstV.convertTo(dstLocV, CV_8UC1);
}
else
{
S8 不考虑透明,将缩放的结果覆盖到画布局部区域
formatSrcY.copyTo(dstLocY);
formatSrcU.copyTo(dstLocU);
formatSrcV.copyTo(dstLocV);

if (dst.isTransparentLayer())
{
cv::Mat& fDstA = dst.getAlpha();
mixlog(MIXLOG << "dst is transparentLayer. fDstA.w:" << fDstA.cols << ", fDstA.h:" << fDstA.rows);
cv::Mat fDstARange = fDstA(cv::Rect(x, y, w, h));
fDstARange.setTo(1.0);
}
}

avpicture_fill((AVPicture*)dstAVFrame, dst.getBuffer(), AV_PIX_FMT_YUV420P, dstW, dstH);
__catch__
return;
}