How to draw rectangle on a YUV420p frame - opencv

I would like to draw a red rectangle using the following function on a YUV420P frame. Following code alters the frame and I can see two black line(top and bottom) remaining black dots scattered. Any suggestions?
void draw_rectangle(uint8_t *frame, int x, int y,
int width, int height,
int img_width, int img_height)
{
cv::Mat frame_yuv;
int size[2];
Point pt1, pt2;
cv::Scalar color = Scalar(255, 0, 0);
size[0] = img_width + img_width/2;
size[1] = img_height;
frame_yuv = cv::Mat(2, size, CV_8UC1, frame);
pt1.x = x;
pt1.y = y;
pt2.x = x + width;
pt2.y = y + height;
rectangle(frame_yuv, pt1, pt2, Scalar(0, 0, 255));
}

Finally, I got my code working. Steps are given below for reference.
frame_yuv = cv::Mat(2, size, CV_8UC3, frame);
cv::Mat C(2,2, CV_8UC3, color);
cv::Mat C_yuv;
cvtColor(C, C_yuv, cv::COLOR_BGR2YUV_I420);
// Set the R, G, B values to C_yuv
// Extract the Y, U, V components to separate Mat's
// Apply rectange first on Y component
// Devide each points pt1, pt2 by 2
// Apply the rectange on U, V
No extra copy of the frame is done.

As you haven't provided any sample data, please use the file kindly provided by #zindarod with dimensions 144x176.
Here is how the YUV data look in memory:
Notice in the stream along the bottom... all the Y pixels come first. Then all the U pixels but downsampled by a factor of 4. Then all the V pixels, also downsampled by a factor of 4.
I haven't got time to write the code in OpenCV, but I can show you how to make a regular Mat out of it.
Step 1 - Extract Y channel
Take the first 144x176 bytes and put them into an 144x176 8UC1 Mat called Y.
Step 2 - Extract U channel
Skip the first 144x176 bytes and then take the next 72x88 bytes and put them into another 72x88 8UC1 Mat called U. Resize this Mat to double the width and double the height, i.e. 144x176.
Step 3 - Extract the V channel
Skip the first (144x176) + (88x72) bytes and then take the next 72x88 bytes and put them into another 72x88 8UC1 Mat called V. Resize this Mat to double the width and double the height, i.e. 144x176.
Step 4 - Merge
Take the Y, U, and V Mats and merge them into an 8UC3 Mat:
// Now merge the 3 individual channels into 3-band bad boy
auto channels = std::vector<cv::Mat>{Y, U, V};
cv::Mat ThreeBandBoy;
cv::merge(channels, ThreeBandBoy);
There is some code here that does more or less exactly what is needed for Steps 1-3.

I am reading this YUV image from file, which is YUV_I420.
fstream file;
file.open("yuv_i420.yuv", ios::in | ios::binary);
// size of image in RGB
size_t rows = 144, cols = 176;
if (!file.is_open())
stderr<<"Error opening file"<<endl;
else {
// get total size of file
auto size = file.tellg();
file.seekg(0,ios::end);
size = file.tellg() - size;
file.seekg(0,ios::beg);
char *buffer = new char[size];
// read image from file
if (file.read(buffer, size)) {
// create YUV Mat
Mat yuv_I420(rows + rows / 2, cols, CV_8UC1, buffer);
// draw a rectangle on YUV image, keep in mind that the YUV image is a
// single channel grayscale image, size is different than the BGR image
rectangle(yuv_I420, Point(10, 10), Point(50, 50), Scalar(255));
// convert to BGR to check validity
Mat bgr;
cvtColor(yuv_I420, bgr, cv::COLOR_YUV2BGR_I420);
cv::imshow("image", bgr);
cv::waitKey(0);
}
file.close();
delete[] buffer;
}

Related

How to undistort I420 image data? Efficiently

I am able to undistort RGB image successfully.
Now, I am working on directly undistort I420 data, instead of first converting it to RGB.
Below are the steps I followed after camera calibration.
K = cv::Matx33d(541.2152931632737, 0.0, 661.7479652584254,
0.0, 541.0606969363056, 317.4524205037745,
0.0, 0.0, 1.0);
D = cv::Vec4d(-0.042166406281296365, -0.001223961942208027, -0.0017036710622692108, 0.00023929900459453295);
newSize = cv::Size(3400, 1940);
cv::Matx33d new_K;
cv::fisheye::estimateNewCameraMatrixForUndistortRectify(K, D, cv::Size(W, H), cv::Mat::eye(3, 3, CV_64F), new_K, 1, newSize); // W,H are the distorted image size
cv::fisheye::initUndistortRectifyMap(K, D, cv::Mat::eye(3, 3, CV_64F), new_K, newSize, CV_16SC2, mapx, mapy);
cv::remap(src, dst, mapx, mapy, cv::INTER_LINEAR);
Above code is giving me undistorted image successfully.
Now I want to undistort I420 data. So, now my src will be an I420/YV12 data.
How can I undistort an I420 data, without converting it first to RGB?
By the way
I420 is an image format with only 1 channel(unlike 3 channels in RGB). It has height = 1.5*image height. Its width is equal to image width.
Below code is to convert I420 to BGR
cvtColor(src, BGR, CV_YUV2BGR_I420, 3);
BGR - pixel arrangement
I420 - pixel arrangement
The most efficient solution is resizing mapx and mapy and applying shrunk maps on down-sampled U and V channels:
Shrink mapx and mapy by a factor of x2 in each axis - create smaller maps matrices.
Divide all elements of shrank maps by 2 (applies mapping lower resolution image).
Apply mapx and mapy on Y color channel.
Apply shrunk_mapx and shrunk_mapy on down-sampled U and V color channels.
Here is a Python OpenCV sample code (please read the comments):
import cv2 as cv
import numpy as np
# For the example, read Y, U and V as separate images.
srcY = cv.imread('DistortedChessBoardY.png', cv.IMREAD_GRAYSCALE) # Y color channel (1280x720)
srcU = cv.imread('DistortedChessBoardU.png', cv.IMREAD_GRAYSCALE) # U color channel (640x360)
srcV = cv.imread('DistortedChessBoardV.png', cv.IMREAD_GRAYSCALE) # V color channel (640x360)
H, W = srcY.shape[0], srcY.shape[1]
K = np.array([[541.2152931632737, 0.0, 661.7479652584254],
[0.0, 541.0606969363056, 317.4524205037745],
[0.0, 0.0, 1.0]])
D = np.array([-0.042166406281296365, -0.001223961942208027, -0.0017036710622692108, 0.00023929900459453295])
# newSize = cv::Size(3400, 1940);
newSize = (850, 480)
# cv::Matx33d new_K;
new_K = np.eye(3)
# cv::fisheye::estimateNewCameraMatrixForUndistortRectify(K, D, cv::Size(W, H), cv::Mat::eye(3, 3, CV_64F), new_K, 1, newSize); // W,H are the distorted image size
new_K = cv.fisheye.estimateNewCameraMatrixForUndistortRectify(K, D, (W, H), np.eye(3), new_K, 1, newSize)
# cv::fisheye::initUndistortRectifyMap(K, D, cv::Mat::eye(3, 3, CV_64F), new_K, newSize, CV_16SC2, mapx, mapy);
mapx, mapy = cv.fisheye.initUndistortRectifyMap(K, D, np.eye(3), new_K, newSize, cv.CV_16SC2);
# cv::remap(src, dst, mapx, mapy, cv::INTER_LINEAR);
dstY = cv.remap(srcY, mapx, mapy, cv.INTER_LINEAR)
# Resize mapx and mapy by a factor of x2 in each axis, and divide each element in the map by 2
shrank_mapSize = (mapx.shape[1]//2, mapx.shape[0]//2)
shrunk_mapx = cv.resize(mapx, shrank_mapSize, interpolation = cv.INTER_LINEAR) // 2
shrunk_mapy = cv.resize(mapy, shrank_mapSize, interpolation = cv.INTER_LINEAR) // 2
# Remap U and V using shunk maps
dstU = cv.remap(srcU, shrunk_mapx, shrunk_mapy, cv.INTER_LINEAR, borderValue=128)
dstV = cv.remap(srcV, shrunk_mapx, shrunk_mapy, cv.INTER_LINEAR, borderValue=128)
cv.imshow('dstY', dstY)
cv.imshow('dstU', dstU)
cv.imshow('dstV', dstV)
cv.waitKey(0)
cv.destroyAllWindows()
Result:
Y:
U:
V:
After converting to RGB:
C++ implementation considerations:
Since I420 format arranges Y, U and V as 3 continuous planes in memory, it's simple to set a pointer to each "plane", and treat it as a Grayscale image.
Same data ordering applies the output image - set 3 pointer to output "planes".
Illustration (assuming even width and height, and assume byte stride equals width):
srcY -> YYYYYYYY dstY -> YYYYYYYYYYYY
YYYYYYYY YYYYYYYYYYYY
YYYYYYYY YYYYYYYYYYYY
YYYYYYYY YYYYYYYYYYYY
YYYYYYYY remap YYYYYYYYYYYY
YYYYYYYY ======> YYYYYYYYYYYY
srcU -> UUUU YYYYYYYYYYYY
UUUU dstU -> YYYYYYYYYYYY
UUUU UUUUUU
srcV -> VVVV UUUUUU
VVVV UUUUUU
VVVV UUUUUU
dstV -> VVVVVV
VVVVVV
VVVVVV
VVVVVV
Implementing above illustration is C++
Under the assumption that width and height are even, and byte stride equals width, you can use the following C++ example for converting I420 to Y, U and V planes:
Assume: srcI420 is Wx(H*3/2) matrix in I420 format, like cv::Mat srcI420(cv::Size(W, H * 3 / 2), CV_8UC1);.
int W = 1280, H = 720; //Assume resolution of Y plane is 1280x720
//Pointer to Y plane
unsigned char *pY = (unsigned char*)srcI420.data;
//Y plane as cv::Mat, resolution of srcY is 1280x720
cv::Mat srcY = cv::Mat(cv::Size(W, H), CV_8UC1, (void*)pY);
//U plane as cv::Mat, resolution of srcU is 640x360 (in memory buffer, U plane is placed after Y).
cv::Mat srcU = cv::Mat(cv::Size(W/2, H/2), CV_8UC1, (void*)(pY + W*H));
//V plane as cv::Mat, resolution of srcV is 640x360 (in memory buffer, V plane is placed after U).
cv::Mat srcV = cv::Mat(cv::Size(W / 2, H / 2), CV_8UC1, (void*)(pY + W*H + (W/2*H/2)));
//Display srcY, srcU, srcV for testing
cv::imshow("srcY", srcY);
cv::imshow("srcU", srcU);
cv::imshow("srcV", srcV);
cv::waitKey(0);
Above example uses pointer manipulations, without the need for copying the data.
You can use the same pointer manipulations for your destination I420 image.
Note: The solution is going to work in most cases, but not guaranteed to work in all cases.
EDIT: Components are not interleaved in the YV12 format, so the following will not work:
If the YV12 data is a one channel image, the interpolation of the remap operation is applied to the value represented by all three YUV data instead of individual Y, U and V components.
Therefore, roughly speaking, instead of doing a
c.YYYYYYYY, c.UU, c.VV
it will perform a
c.YYYYYYYYUUVV
during a linear interpolation.
You can perform a YV12 -> BGR color conversion after remap, but the colors of the interpolated pixels would be wrong.
Instead of doing a linear interpolation, try using a nearest-neighbor interpolation in remap. Then you should be able to get correct colors after YV12 -> BGR color conversion.
So, find mapx, mapy, then remap using INTER_NEAREST, and finally perform a YV12 -> BGR color conversion.

how to get all undistorted image with opencv

I'm unsing cv::undistort but it crops the image. I'd like to have all the undistorted image, so that the undistorted size is bigger then the original one, like this:
I think I need to use cv::getOptimalNewCameraMatrix but I had no luck with my trials.. some help?
Just for the record:
You should use cv::getOptimalNewCameraMatrix and set the alpha parameter to 1. Alpha 0 only shows valid points on the image, alpha 1 shows all the original points as well as black regions. cv::getOptimalNewCameraMatrix aslo gives you a ROI to crop the result from cv::undistort.
This code would do the trick:
void loadUndistortedImage(std::string fileName, Mat & outputImage,
Mat & cameraMatrix, Mat & distCoeffs) {
Mat image = imread(fileName, CV_LOAD_IMAGE_GRAYSCALE);
// setup enlargement and offset for new image
double y_shift = 60;
double x_shift = 70;
Size imageSize = image.size();
imageSize.height += 2*y_shift;
imageSize.width += 2*x_shift;
// create a new camera matrix with the principal point
// offest according to the offset above
Mat newCameraMatrix = cameraMatrix.clone();
newCameraMatrix.at<double>(0, 2) += x_shift; //adjust c_x by x_shift
newCameraMatrix.at<double>(1, 2) += y_shift; //adjust c_y by y_shift
// create undistortion maps
Mat map1;
Mat map2;
initUndistortRectifyMap(cameraMatrix, distCoeffs, Mat(),
newCameraMatrix, imageSize, CV_16SC2, map1, map2);
//remap
remap(image, outputImage, map1, map2, INTER_LINEAR);
}
See http://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html
and http://docs.opencv.org/2.4/modules/imgproc/doc/geometric_transformations.html
the best would be subclass OpenCV's class and overload the method undistort(), in order to access all the images you need.

How to access each channel of a pixel using cuda tex2D

I'm learning cuda texture memory. Now, I got a opencv Iplimage, and I get its imagedata. Then I bind a texture to this uchar array, like below:
Iplimage *image = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 3);
unsigned char* imageDataArray = (unsigned char*)image->imagedata;
texture<unsigned char,2,cudaReadModeElementType> tex;
cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc(8, 8, 8, 0,
cudaChannelFormatKindUnsigned);
cudaArray *cuArray = NULL;
CudaSafeCall(cudaMallocArray(&cuArray,&channelDesc,width,height));
cudaMemcpy2DToArray(cuArray,0,0,imageDataArray,image->widthstep,
width * sizeof(unsigned char), height, cudaMemcpyHostToDevice);
cudaBindTextureToArray(texC1_cf,cuArray_currentFrame, channelDesc);
Now I lanch my kernel, and I want to access each pixel, every channel of that image. This is where I get confused.
I use this code to get the pixel coordinate (X,Y):
int X = (blockIdx.x*blockDim.x+threadIdx.x);
int Y = (blockIdx.y*blockDim.y+threadIdx.y);
And how can I access each channel of this (X,Y)? what's the code below return?
tex2D(tex, X, Y);
Besides this, Can you tell me how texture memory using texture to access an array, and how this transform looks like?
To bind a 3 channel OpenCV image to cudaArray texture, you have to create a cudaArray of width equal to image->width * image->nChannels, because the channels are stored interleaved by OpenCV.
cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc<unsigned char>();
cudaArray *cuArray = NULL;
CudaSafeCall(cudaMallocArray(&cuArray,&channelDesc,width * image->nChannels,height));
cudaMemcpy2DToArray(cuArray,0,0,imageDataArray,image->widthstep, width * image->nChannels * sizeof(unsigned char), height, cudaMemcpyHostToDevice);
cudaBindTextureToArray(texC1_cf,cuArray_currentFrame, channelDesc);
Now, to access each channel separately in the kernel, you just have to multiply the x index with number of channels and add the offset of desired channel like this:
unsigned char blue = tex2D(tex, (3 * X) , Y);
unsigned char green = tex2D(tex, (3 * X) + 1, Y);
unsigned char red = tex2D(tex, (3 * X) + 2, Y);
First one is blue because OpenCV stores images with channel sequence BGR.
As for the error you get when you try to access texture<uchar3,..> using tex2D; CUDA only supports creating 2D textures of 1,2 and 4 element vector types. Unfortunately, ONLY 3 is not supported which is very good for binding RGB images and is a really desirable feature.

In openCV, how to replace an RGB ROI in image

I have an RGB large-image, and an RGB small-image.
What is the fastest way to replace a region in the larger image with the smaller one?
Can I define a multi-channel ROI and then use copyTo? Or must I split each image to channels, replace the ROI and then recombine them again to one?
Yes. A multi channel ROI and copyTo will work. Something like:
int main(int argc,char** argv[])
{
cv::Mat src = cv::imread("c:/src.jpg");
//create a canvas with 10 pixels extra in each dim. Set all pixels to yellow.
cv::Mat canvas(src.rows + 20, src.cols + 20, CV_8UC3, cv::Scalar(0, 255, 255));
//create an ROI that will map to the location we want to copy the image into
cv::Rect roi(10, 10, src.cols, src.rows);
//initialize the ROI in the canvas. canvasROI now points to the location we want to copy to.
cv::Mat canvasROI(canvas(roi));
//perform the copy.
src.copyTo(canvasROI);
cv::namedWindow("original", 256);
cv::namedWindow("canvas", 256);
cv::imshow("original", src);
cv::imshow("canvas", canvas);
cv::waitKey();
}

Segmenting and masking all shades of red from an image using opencv

I am trying to segment all shades of red form an image using hue saturation values and use InRangeS function to create a mask which should have all red areas whitened and all others blacked(a new 1 channel image). Thwn Inpaint them to kind of obscure the segmented portions.
My code is as given.
However I am unable to get an output image, it doesnt segment the desired color range.
Any pointers on my approach and why it isnt working. ?
int main(){
IplImage *img1=cvLoadImage("/home/techrascal/projects/test1/image2.jpeg");
//IplImage *img3;
IplImage *imghsv;
IplImage *img4;
CvSize sz=cvGetSize(img1);
imghsv=cvCreateImage(sz,IPL_DEPTH_8U,3);
img4=cvCreateImage(sz,IPL_DEPTH_8U,1);
int width = img1->width;
int height = img1->height;
int bpp = img1->nChannels;
//int w=img4->width;
//int h=img4->height;
//int bn=img4->nChannels;
cvNamedWindow("original", 1);
cvNamedWindow("hsv",1);
cvNamedWindow("Blurred",1);
int r,g,b;
// create inpaint mask: img 4 will behave as mask
cvCvtColor(img1,imghsv,CV_BGR2HSV);
CvScalar hsv_min = cvScalar(0, 0, 0, 0);
CvScalar hsv_max = cvScalar(255, 0, 0, 0);
//cvShowImage("hsv",imghsv);
cvInRangeS( imghsv, hsv_min, hsv_max, img4 );
cvInpaint(img1, img4, img1, 3,CV_INPAINT_NS );
cvShowImage("Blurred",img1);
cvReleaseImage(&img1);
cvReleaseImage(&imghsv);
cvReleaseImage(&img4);
//cvReleaseImage(&img3);
char d=cvWaitKey(10000);
cvDestroyAllWindows();
return 0;}
Your code logic seems correct but you will definetely need to adjust your hsv range values
(hsv_min and hsv_max).
Read this detailed guide that show you hsv range defined in opencv
http://www.shervinemami.co.cc/colorConversion.html

Resources