Color Space Transform And Geometry Transform

Color Space ,Transform, Threshold

Mat green=Mat(1,1,CV_8UC,Scalar(0,255,0)),hsv_green;
cv::cvtColor(green,hsv_green,cv::COLOR_BGR2HSV);
vec<uint8_t,3> value=hsv_green.at<vec<uint8_t,3>>(0,0);

Now you take [H-10, 100,100] and [H+10, 255, 255] as the lower bound and upper bound respectively. 

Object Color Tracking by inRange

    # Take each frame
    _, frame = cap.read()
    # Convert BGR to HSV
    hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
    # define range of blue color in HSV
    lower_blue = np.array([110,50,50])
    upper_blue = np.array([130,255,255])
    # Threshold the HSV image to get only blue colors
    mask = cv.inRange(hsv, lower_blue, upper_blue)
    # Bitwise-AND mask and original image
    res = cv.bitwise_and(frame,frame, mask= mask)

Transformations

OpenCV provides two transformation functions, warpAffine and warpPerspective, with which you can perform all kinds of transformations. warpAffine takes a 2×3 transformation matrix while warpPerspective takes a 3×3 transformation matrix as input.

resize Imgae
Scaling is just resizing of the image. OpenCV comes with a function resize() for this purpose. The size of the image can be specified manually, or you can specify the scaling factor. Different interpolation methods are used. Preferable interpolation methods are INTER_AREA for shrinking and INTER_CUBIC (slow) & INTER_LINEAR for zooming. By default, the interpolation method INTER_LINEAR is used for all resizing purposes. You can resize an input image with either of following methods:

resize(LoadedImage, LoadedImage, Size(100, 100));

Tanslate

warpAffine( src, warp_dst, warp_mat, warp_dst.size() );
translation.jpg

Rotation

Mat cv::getRotationMatrix2D	(	Point2f 	center,
double 	angle,
double 	scale 
)	
M = getRotationMatrix2D(((cols-1)/2.0,(rows-1)/2.0),90,1)
warpAffine(img,dest,M,(cols,rows))
rotation.jpg

Affine Transformation

In affine transformation, all parallel lines in the original image will still be parallel in the output image. To find the transformation matrix, we need three points from the input image and their corresponding locations in the output image. Then getAffineTransform will create a 2×3 matrix which is to be passed to warpAffine.


Mat cv::getAffineTransform	(	const Point2f 	src[],
const Point2f 	dst[] 
)	

Parameters
src	Coordinates of triangle vertices in the source image.
dst	Coordinates of the corresponding triangle vertices in the destination image.
affine.jpg

Perspective Transformation

For perspective transformation, you need a 3×3 transformation matrix. Straight lines will remain straight even after the transformation. To find this transformation matrix, you need 4 points on the input image and corresponding points on the output image. Among these 4 points, 3 of them should not be collinear. Then the transformation matrix can be found by the function getPerspectiveTransform. Then apply warpPerspective with this 3×3 transformation matrix.


Mat cv::getPerspectiveTransform	(	InputArray 	src,
InputArray 	dst,
int 	solveMethod = DECOMP_LU 
)	
pts1 = np.float32([[56,65],[368,52],[28,387],[389,390]])
pts2 = np.float32([[0,0],[300,0],[0,300],[300,300]])
Mat M = getPerspectiveTransform(pts1,pts2)
warpPerspective(img,dst,M,(300,300))
perspective.jpg

Gradient And Edge Detection With OpenCV

Edge Detection With Canny

1- RGB to Gray

2- Noise Removal (Gaussian Filter)

3- Edge Detection (Sobel Operator)

4-Edge Thinning ( Non Maximal Supression )

5- Connect Week Edges (Hysteresis Thresholding) (Double Threshold)

https://towardsdatascience.com/canny-edge-detection-step-by-step-in-python-computer-vision-b49c3a2d8123

Mat src, src_gray;
Mat dst, detected_edges;
int lowThreshold = 0;
const int max_lowThreshold = 100;
const int ratio = 3;
const int kernel_size = 3;
const char* window_name = "Edge Map";
static void CannyThreshold(int, void*)
{
    blur( src_gray, detected_edges, Size(3,3) );
    Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );
    dst = Scalar::all(0);
    src.copyTo( dst, detected_edges);
    imshow( window_name, dst );
}

Gradient With Sobel , Scharr and Laplacian

OpenCV provides three types of gradient filters or High-pass filters Or Sharpening Filter Sobel, Scharr and Laplacian. We will see each one of them.

1. Sobel and Scharr Derivatives
Sobel operators is a joint Gausssian smoothing plus differentiation operation, so it is more resistant to noise. You can specify the direction of derivatives to be taken, vertical or horizontal (by the arguments, yorder and xorder respectively). You can also specify the size of kernel by the argument ksize. If ksize = -1, a 3×3 Scharr filter is used which gives better results than 3×3 Sobel filter. Please see the docs for kernels used

2- Laplacian
It calculates the Laplacian of the image given by the relation

img = cv.imread('dave.jpg',0)
laplacian = cv.Laplacian(img,cv.CV_64F)
sobelx = cv.Sobel(img,cv.CV_64F,1,0,ksize=5)
sobely = cv.Sobel(img,cv.CV_64F,0,1,ksize=5)
gradients.jpg

If you want to detect both edges, better option is to keep the output datatype to some higher forms, like cv.CV_16S, cv.CV_64F etc, take its absolute value and then convert back to cv.CV_8U. Below code demonstrates this procedure for a horizontal Sobel filter and difference in results.


# Output dtype = cv.CV_8U
sobelx8u = cv.Sobel(img,cv.CV_8U,1,0,ksize=5)
# Output dtype = cv.CV_64F. Then take its absolute and convert to cv.CV_8U
sobelx64f = cv.Sobel(img,cv.CV_64F,1,0,ksize=5)
abs_sobel64f = np.absolute(sobelx64f)
sobel_8u = np.uint8(abs_sobel64f)
double_edge.jpg

also, you can use dilate or erode functions to increase or decrease edges thickness

void Erosion( int, void* )
{
  int erosion_type = 0;
  if( erosion_elem == 0 ){ erosion_type = MORPH_RECT; }
  else if( erosion_elem == 1 ){ erosion_type = MORPH_CROSS; }
  else if( erosion_elem == 2) { erosion_type = MORPH_ELLIPSE; }
  Mat element = getStructuringElement( erosion_type,
                       Size( 2*erosion_size + 1, 2*erosion_size+1 ),
                       Point( erosion_size, erosion_size ) );
  erode( src, erosion_dst, element );
  imshow( "Erosion Demo", erosion_dst );
}

void Dilation( int, void* )
{
  int dilation_type = 0;
  if( dilation_elem == 0 ){ dilation_type = MORPH_RECT; }
  else if( dilation_elem == 1 ){ dilation_type = MORPH_CROSS; }
  else if( dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; }
  Mat element = getStructuringElement( dilation_type,
                       Size( 2*dilation_size + 1, 2*dilation_size+1 ),
                       Point( dilation_size, dilation_size ) );
  dilate( src, dilation_dst, element );
  imshow( "Dilation Demo", dilation_dst );
}

Blurring and Filtering images With OpenCV

2D Convolution ( Image Filtering )

As in one-dimensional signals, images also can be filtered with various low-pass filters (LPF), high-pass filters (HPF), etc. LPF helps in removing noise, blurring images, etc. HPF filters help in finding edges in images
OpenCV provides a function cv.filter2D() to convolve a kernel with an image.

Normalized Box Filter

To perform a smoothing operation we will apply a filter to our image. The most common type of filters are linear, in which an output pixel’s value .

g(i,j) = \sum_{k,l} f(i+k, j+l) h(k,l)
kernel = Mat::ones(5, 5, CV_8UC);/25
dst = filter2D(img,-1,kernel)
//or
blur = blur(img,dest,5)

// or you can use boxFilter
filter2D(InputArray src, OutputArray dst, int ddepth, InputArray kernel, Point anchor=Point(-1,-1), double delta=0, int borderType=BORDER_DEFAULT )

Median Filter

The median filter run through each element of the signal (in this case the image) and replace each pixel with the median of its neighboring pixels

median.jpg
medianBlur ( src, dst, i );//i is kernal size

Gaussian Filter

  • Probably the most useful filter (although not the fastest). Gaussian filtering is done by convolving each point in the input array with a Gaussian kernel and then summing them all to produce the output array.
  • Just to make the picture clearer, remember how a 1D Gaussian
../../../../_images/Smoothing_Tutorial_theory_gaussian_0.jpg
  • Assuming that an image is 1D, you can notice that the pixel located in the middle would have the biggest weight. The weight of its neighbors decreases as the spatial distance between them and the center pixel increases.
gaussian.jpg

Gaussian Filter Generation

G(x, y)=\frac{1}{2\pi \sigma ^{2}}e^{-\frac{x^{2}+y^{2}}{2\sigma ^{2}}}
void FilterCreation(double GKernel[][5]) 
{ 
    // intialising standard deviation to 1.0 
    double sigma = 1.0; 
    double r, s = 2.0 * sigma * sigma; 
  
    // sum is for normalization 
    double sum = 0.0; 
  
    // generating 5x5 kernel 
    for (int x = -2; x <= 2; x++) { 
        for (int y = -2; y <= 2; y++) { 
            r = sqrt(x * x + y * y); 
            GKernel[x + 2][y + 2] = (exp(-(r * r) / s)) / (M_PI * s); 
            sum += GKernel[x + 2][y + 2]; 
        } 
    } 
  
    // normalising the Kernel 
    for (int i = 0; i < 5; ++i) 
        for (int j = 0; j < 5; ++j) 
            GKernel[i][j] /= sum; 
} 
0.00296902    0.0133062    0.0219382    0.0133062    0.00296902    
0.0133062    0.0596343    0.0983203    0.0596343    0.0133062    
0.0219382    0.0983203    0.162103    0.0983203    0.0219382    
0.0133062    0.0596343    0.0983203    0.0596343    0.0133062    
0.00296902    0.0133062    0.0219382    0.0133062    0.00296902

Bilateral Filter

  • So far, we have explained some filters which main goal is to smooth an input image. However, sometimes the filters do not only dissolve the noise, but also smooth away the edges. To avoid this (at certain extent at least), we can use a bilateral filter.
  • In an analogous way as the Gaussian filter, the bilateral filter also considers the neighboring pixels with weights assigned to each of them. These weights have two components, the first of which is the same weighting used by the Gaussian filter. The second component takes into account the difference in intensity between the neighboring pixels and the evaluated one.
bilateralFilter(InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType=BORDER_DEFAULT )
Parameters:	
src – Source 8-bit or floating-point, 1-channel or 3-channel image.
dst – Destination image of the same size and type as src .
d – Diameter of each pixel neighborhood that is used during filtering. If it is non-positive, it is computed from sigmaSpace .
sigmaColor – Filter sigma in the color space. A larger value of the parameter means that farther colors within the pixel neighborhood (see sigmaSpace ) will be mixed together, resulting in larger areas of semi-equal color.
sigmaSpace – Filter sigma in the coordinate space. A larger value of the parameter means that farther pixels will influence each other as long as their colors are close enough (see sigmaColor ). When d>0 , it specifies the neighborhood size regardless of sigmaSpace . Otherwise, d is proportional to sigmaSpace .
bilateral.jpg

OpenCV Mat

You will need to include only the opencv2/opencv.hpp header file in your program
Add the using namespace cv; line just after including header files

#include <opencv2/opencv.hpp>
using namespace cv;
#include <opencv2/opencv.hpp> 

int main(int argc, char** argv) 
{ 
 // Read the image file 
 cv::Mat image = cv::imread("D:/My OpenCV Website/Eagle.jpg"); 
 return 0; 
}

Data Types of Arrays

Any single channel array should belong to one of following data types.

  • CV_8U – 8 bit unsigned integer
  • CV_8S – 8 bit signed integer
  • CV_16U – 16 bit unsigned integer
  • CV_16S – 16 bit signed integer
  • CV_32S – 32 bit signed integer
  • CV_32F – 32 bit floating point number
  • CV_64F – 64 bit float floating point number

We can define all of above data types for multi channel arrays. OpenCV supports up to 512 channels. Here I am going to show you how to define CV_8U data type for multi channel arrays.

  • CV_8UC1 – Single channel array with 8 bit unsigned integers which is exactly same as CV_8U
  • CV_8UC2 – 2 channel array with 8 bit unsigned integers
  • CV_8UC3 – 3 channel array with 8 bit unsigned integers
  • CV_8UC4 – 4 channel array with 8 bit unsigned integers
  • CV_8UC(n) – n channel array with 8 bit unsigned integers (n can be from 1 to 512) )

Mat

The class Mat represents an n-dimensional dense numerical single-channel or multi-channel array. It can be used to store real or complex-valued vectors and matrices, grayscale or color images, voxel volumes, vector fields, point clouds, tensors, histograms .
 M.step[i] >= M.step[i+1]

//3 channels 
 //image depth of 8 bits 
 //800 x 600 of resolution (800 wide and 600 high)
 //each pixels initialized to the value of (100, 250, 30) for Blue, Green and Red planes respectively.
 Mat image(600, 800, CV_8UC3, Scalar(100, 250, 30)); 
Mat ()
 
Mat (int rows, int cols, int type)
 
Mat (Size size, int type)
 
Mat (int rows, int cols, int type, const Scalar &s)
 
Mat (Size size, int type, const Scalar &s)
 
Mat (int ndims, const int *sizes, int type)
 
Mat (const std::vector< int > &sizes, int type)
 
Mat (int ndims, const int *sizes, int type, const Scalar &s)//multi dimensional mat
 
Mat (const std::vector< int > &sizes, int type, const Scalar &s)//multi dimensional mat
 
Mat (const Mat &m)//
 
Mat (int rows, int cols, int type, void *data, size_t step=AUTO_STEP)

Mat (const Mat &m, const Rect &roi)//Mat 	operator() (const Rect &roi) const

Mat (const Mat &m, const Range *ranges)//Mat 	operator() (const Range &roi) const

Use a copy constructor or assignment operator where there can be an array or expression on the right side (see below). As noted in the introduction, the array assignment is an O(1) operation because it only copies the header and increases the reference counter. The Mat::clone() method can be used to get a full (deep) copy of the array when you need it.

// make a 7x7 complex matrix filled with 1+3j.
Mat M(7,7,CV_32FC2,Scalar(1,3));
// and now turn M to a 100x60 15-channel 8-bit matrix.
// The old content will be deallocated
M.create(100,60,CV_8UC(15));
// create a 100x100x100 8-bit array
int sz[] = {100, 100, 100};
Mat bigCube(3, sz, CV_8U, Scalar::all(0));
// add the 5-th row, multiplied by 3 to the 3rd row
M.row(3) = M.row(3) + M.row(5)*3;
// now copy the 7-th column to the 1-st column
// M.col(1) = M.col(7); // this will not work
Mat M1 = M.col(1);
M.col(7).copyTo(M1);
// create a new 320x240 image
Mat img(Size(320,240),CV_8UC3);
// select a ROI
Mat roi(img, Rect(10,10,100,100));
// fill the ROI with (0,255,0) (which is green in RGB space);
// the original 320x240 image will be modified
roi = Scalar(0,255,0);
//locateROI
Mat A = Mat::eye(10, 10, CV_32S);
// extracts A columns, 1 (inclusive) to 3 (exclusive).
Mat B = A(Range::all(), Range(1, 3));
// extracts B rows, 5 (inclusive) to 9 (exclusive).
// that is, C \~ A(Range(5, 9), Range(1, 3))
Mat C = B(Range(5, 9), Range::all());
Size size; Point ofs;
C.locateROI(size, ofs);
// size will be (width=10,height=10) and the ofs will be (x=1, y=5)
double m[3][3] = {{a, b, c}, {d, e, f}, {g, h, i}};
Mat M = Mat(3, 3, CV_64F, m).inv();
//Use MATLAB-style array initializers, zeros(), ones(), eye()
// create a double-precision identity matrix and add it to M.
M += Mat::eye(M.rows, M.cols, CV_64F);
// create a 3x3 double-precision identity matrix
Mat M = (Mat_<double>(3,3) << 1, 0, 0, 0, 1, 0, 0, 0, 1);

 If you want to release the data pointed by a array header before the array destructor is called, use Mat::release().

// you can access the element Mij of a 2-dimensional array as:
M.at<double>(i,j) += 1.f;
//rows ptr
// compute sum of positive matrix elements
// (assuming that M is a double-precision matrix)
double sum=0;
for(int i = 0; i < M.rows; i++)
{
    const double* Mi = M.ptr<double>(i);
    for(int j = 0; j < M.cols; j++)
        sum += std::max(Mi[j], 0.);
}

matrix iterator
The matrix iterators are random-access iterators, so they can be passed to any STL algorithm, including std::sort().

// compute sum of positive matrix elements, iterator-based variant
double sum=0;
MatConstIterator_<double> it = M.begin<double>(), it_end = M.end<double>();
for(; it != it_end; ++it)
    sum += std::max(*it, 0.);
//rows and cols and at
Mat H(100, 100, CV_64F);
for(int i = 0; i < H.rows; i++)
    for(int j = 0; j < H.cols; j++)
        H.at<double>(i,j)=1./(i+j+1);
// alternative implementation of Mat::isContinuous()
bool myCheckMatContinuity(const Mat& m)
{
    //return (m.flags & Mat::CONTINUOUS_FLAG) != 0;
    return m.rows == 1 || m.step == m.cols*m.elemSize();
}
//foreach
// Creating 3D matrix (255 x 255 x 255) typed uint8_t
// and initialize all elements by the value which equals elements position.
// i.e. pixels (x,y,z) = (1,2,3) is (b,g,r) = (1,2,3).
int sizes[] = { 255, 255, 255 };
typedef cv::Point3_<uint8_t> Pixel;
Mat_<Pixel> image = Mat::zeros(3, sizes, CV_8UC3);
image.forEach<Pixel>([&](Pixel& pixel, const int position[]) -> void {
    pixel.x = position[0];
    pixel.y = position[1];
    pixel.z = position[2];
});
//reshape
std::vector<Point3f> vec;
...
Mat pointMat = Mat(vec). // convert vector to Mat, O(1) operation
                  reshape(1). // make Nx3 1-channel matrix out of Nx1 3-channel.
                              // Also, an O(1) operation
                     t(); // finally, transpose the Nx3 matrix.
                          // This involves copying all the elements

clone() :
The method creates a full copy of the array .

copyTo(OutputArray) :
The method copies the matrix data to another matrix . Before copying the data, the method invokes : ceate(size,type) .

Mat row(int y) const :
The method makes a new header for the specified matrix row and returns it .

Mat cv::Mat::rowRange( int startrow,int endrow) const :
Creates a matrix header for the specified row span. startrow is inclusive and endrow is exclusive .

same way with col(int) and colRange(int,int)

isSubmatrix() :
return true if the matrix is submatrix of other matrix .

isContinuous():
m.rows == 1 || m.step == m.cols*m.elemSize() so supmatrix is not continues because m.step is the full row elements size but m.cols is sub of original .

adjustROI(int dtop, int dbottom, int dleft, int dright):
Adjusts a submatrix size and position within the parent matrix.

locateROI(Size,Point):
save size of full matrix and offset x and y to point .

reshape(int channels,dimensions) :
Changes the shape and/or the number of channels of a 2D matrix without copying the data.

convertTo(OutputArray m,int rtype,double alpha=1,double beta=0)
m(x,y)=saturate_cast<rType>(α(∗this)(x,y)+β)

Matrix expression representation.

This is a list of implemented matrix operations that can be combined in arbitrary complex expressions (here A, B stand for matrices ( Mat ), s for a scalar ( Scalar ), alpha for a real-valued scalar ( double )):

  • Addition, subtraction, negation: A+BA-BA+sA-ss+As-A-A
  • Scaling: A*alpha
  • Per-element multiplication and division: A.mul(B)A/Balpha/A
  • Matrix multiplication: A*B
  • Transposition: A.t() (means AT)
  • Matrix inversion and pseudo-inversion, solving linear systems and least-squares problems: A.inv([method]) (~ A<sup>-1</sup>)A.inv([method])*B (~ X: AX=B)
  • Comparison: A cmpop BA cmpop alphaalpha cmpop A, where cmpop is one of >>===!=<=<. The result of comparison is an 8-bit single channel mask whose elements are set to 255 (if the particular element or pair of elements satisfy the condition) or 0.
  • Bitwise logical operations: A logicop BA logicop ss logicop A~A, where logicop is one of &|^.
  • Element-wise minimum and maximum: min(A, B)min(A, alpha)max(A, B)max(A, alpha)
  • Element-wise absolute value: abs(A)
  • Cross-product, dot-product: A.cross(B)A.dot(B)
  • Any function of matrix or matrices and scalars that returns a matrix or a scalar, such as norm, mean, sum, countNonZero, trace, determinant, repeat, and others.
  • Matrix initializers ( Mat::eye()Mat::zeros()Mat::ones() ), matrix comma-separated initializers, matrix constructors and operators that extract sub-matrices (see Mat description).
  • Mat_<destination_type>() constructors to cast the result to the proper type.
Mat m1= Mat::eye(2,3, CV_32F); 
Mat m2= Mat::ones(3,2, CV_32F); 
// Scalar by matrix 
cout << "nm1.*2n" << m1*2 << endl; 
// matrix per element multiplication 
cout << "n(m1+2).*(m1+3)n" << (m1+1).mul(m1+3) << endl; 
// Matrix multiplication 
cout << "nm1*m2n" << m1*m2 << endl; 

QImage to Mat and Opposit

image= QImage("C:/Users/malabdali/Pictures/elementary linear algebra.png");
qDebug()<<image.depth();//32 bits depth
m=Mat(image.height(),image.width(),CV_8UC(4),image.bits());
qDebug()<<m.step[0]<<m.step[1]<<" = "<<m.elemSize()<<m.size[1]<<m.type();//2380 number of bytes for row,4 number of bytes for pixel
QImage img((uchar*)mat.data, mat.cols, mat.rows, QImage::Format_RGB32);

most popular type classes

//2d points
typedef Point_<int> Point2i;
typedef Point2i Point;
typedef Point_<float> Point2f;
typedef Point_<double> Point2d;
//3d points
typedef Point3_<int> Point3i;
typedef Point3_<float> Point3f;
typedef Point3_<double> Point3d;
//vectors
typedef Vec<uchar, 3> Vec3b;
// 	Scalar (double v0, double v1, double v2=0, double v3=0)
Mat image(600, 800, CV_8UC3, Scalar(100, 250, 30)); 
//Size Rect RotatedRect Point Range
Mat m(image.height(),image.width(),CV_8UC(4),image.bits());
Vec<uint8_t,4> v=m.at<Vec<uint8_t,4>>(200,200);
qDebug()<<v[0]<<v[1]<<v[2];

how to make assignment by your self

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;
}

or use 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;
}

on the fly address

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;
}

lookup table modification cv::LUT()
Performs a look-up table transform of an array.
The function LUT fills the output array with values from the look-up table. Indices of the entries are taken from the input array. That is, the function processes each element of src as follows:dst(I)←lut(src(I) )

srcinput array of 8-bit elements.
lutlook-up table of 256 elements; in case of multi-channel input array, the table should either have a single channel (in this case the same table is used for all channels) or the same number of channels as in the input array.
dstoutput array of the same size and number of channels as src, and the same depth as lut.
Mat lookUpTable(1, 256, CV_8U);
uchar* p = lookUpTable.ptr();
for( int i = 0; i < 256; ++i)
        p[i] = table[i];
LUT(I, lookUpTable, J);

sharpen image example (filter2d or .ptr<>(i,j))

#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();
    Mat kernel = (Mat_<char>(3,3) <<  0, -1,  0,
                                   -1,  5, -1,
                                    0, -1,  0);
    t = (double)getTickCount();
    filter2D( src, dst1, src.depth(), kernel );
    t = ((double)getTickCount() - t)/getTickFrequency();
    cout << "Built-in filter2D time passed in seconds:     " << t << endl;
    imshow( "Output", dst1 );
    waitKey();
    return EXIT_SUCCESS;
}
void Sharpen(const Mat& myImage,Mat& Result)
{
    CV_Assert(myImage.depth() == CV_8U);  // accept only uchar images
    const int nChannels = myImage.channels();
    Result.create(myImage.size(),myImage.type());
    for(int j = 1 ; j < myImage.rows-1; ++j)
    {
        const uchar* previous = myImage.ptr<uchar>(j - 1);
        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]);
        }
    }
    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));
}