How to blur an image after obtaining its mask using opencv?
Question:
I’ve followed a guide on blurring image based on colour segmentation: https://realpython.com/python-opencv-color-spaces/
But I have trouble in getting it to blur only the nemo in the original image.
What I have so far from following the guide:
import matplotlib.pyplot as plt
import cv2
nemo = cv2.imread('nemo.png')
nemo = cv2.cvtColor(nemo, cv2.COLOR_BGR2RGB)
hsv_nemo = cv2.cvtColor(nemo, cv2.COLOR_RGB2HSV)
light_orange = (1, 190, 200)
dark_orange = (18, 255, 255)
light_white = (0, 0, 200)
dark_white = (145, 60, 255)
mask = cv2.inRange(hsv_nemo, light_orange, dark_orange)
mask_white = cv2.inRange(hsv_nemo, light_white, dark_white)
final_mask = mask + mask_white
final_result = cv2.bitwise_and(nemo, nemo, mask=final_mask)
blur = cv2.blur(final_result, (15, 15), 0)
I’ve plotted out 1) nemo
, 2) final_mask
, and 3) blur
for comparison here: Image Result
Is there a way for me to blur the nemo in the original photo such that it looks something like this: Blurred Original Photo
At the moment it only blurs nemo in the mask image.
I think I need to obtain a ROI for me to do so, but how do I form the relationship between a mask and getting the coordinates/ ROI of the nemo from the original photo? Thanks!
Answers:
If you wanted to blur the image only where the mask is true, something like this should work
blur = cv2.blur(nemo,(15,15),0)
out = nemo.copy()
out[mask>0] = blur[mask>0]
However, you may want to change how the mask has been generated, as currently it will only blur the orange and white parts of the fish.
Here is an answer that works on a Vec3b but only looks at channel 0.
It respects the mask and only blurs where there is not a mask. This prevents "halo" effects on the edges of interior masked areas. In my case it is a ball with the outside of circle masked out and also the glare spot reflected off the ball.
void MyPipelineCPU::MyBlur(Mat& src, Mat& mask, Mat& dst, const int diameter) {
if (dst.empty() || dst.rows != src.rows || dst.cols != src.cols || dst.type() != src.type())
dst = Mat::zeros(src.rows, src.cols, src.type());
const int diamter2 = diameter * diameter;
const int radius = diameter / 2;
const int radius2 = radius * radius;
const int rowmax = src.rows - radius;
const int colmax = src.cols - radius;
for (int r = radius; r < rowmax; r++) {
for (int c = radius; c < colmax; c++) {
uchar msk = mask.at<uchar>(r, c);
if (msk) {
Vec3b& srcP = src.at<Vec3b>(r, c);
Vec3b& dstP = dst.at<Vec3b>(r, c);
// It is treated as a grey image even though three channels
int sum0 = 0;
int count0 = 0;
for (int dy = -radius; dy <= radius; dy++) {
for (int dx = -radius; dx <= radius; dx++) {
if (mask.at<uchar>(r + dy, c + dx)) {
const Vec3b& pp = src.at<Vec3b>(r + dy, c + dx);
sum0 += pp[0];
count0++;
}
}
}
if (count0 > 4) {
const int avg0 = sum0 / count0;
// Blur
dstP[0] = avg0;
dstP[1] = avg0;
dstP[2] = avg0;
}
else {
dstP[0] = 0;
dstP[1] = 0;
dstP[2] = 0;
}
}
else {
Vec3b& dstP = dst.at<Vec3b>(r, c);
dstP[0] = 0;
dstP[1] = 0;
dstP[2] = 0;
}
}
}
}
I’ve followed a guide on blurring image based on colour segmentation: https://realpython.com/python-opencv-color-spaces/
But I have trouble in getting it to blur only the nemo in the original image.
What I have so far from following the guide:
import matplotlib.pyplot as plt
import cv2
nemo = cv2.imread('nemo.png')
nemo = cv2.cvtColor(nemo, cv2.COLOR_BGR2RGB)
hsv_nemo = cv2.cvtColor(nemo, cv2.COLOR_RGB2HSV)
light_orange = (1, 190, 200)
dark_orange = (18, 255, 255)
light_white = (0, 0, 200)
dark_white = (145, 60, 255)
mask = cv2.inRange(hsv_nemo, light_orange, dark_orange)
mask_white = cv2.inRange(hsv_nemo, light_white, dark_white)
final_mask = mask + mask_white
final_result = cv2.bitwise_and(nemo, nemo, mask=final_mask)
blur = cv2.blur(final_result, (15, 15), 0)
I’ve plotted out 1) nemo
, 2) final_mask
, and 3) blur
for comparison here: Image Result
Is there a way for me to blur the nemo in the original photo such that it looks something like this: Blurred Original Photo
At the moment it only blurs nemo in the mask image.
I think I need to obtain a ROI for me to do so, but how do I form the relationship between a mask and getting the coordinates/ ROI of the nemo from the original photo? Thanks!
If you wanted to blur the image only where the mask is true, something like this should work
blur = cv2.blur(nemo,(15,15),0)
out = nemo.copy()
out[mask>0] = blur[mask>0]
However, you may want to change how the mask has been generated, as currently it will only blur the orange and white parts of the fish.
Here is an answer that works on a Vec3b but only looks at channel 0.
It respects the mask and only blurs where there is not a mask. This prevents "halo" effects on the edges of interior masked areas. In my case it is a ball with the outside of circle masked out and also the glare spot reflected off the ball.
void MyPipelineCPU::MyBlur(Mat& src, Mat& mask, Mat& dst, const int diameter) {
if (dst.empty() || dst.rows != src.rows || dst.cols != src.cols || dst.type() != src.type())
dst = Mat::zeros(src.rows, src.cols, src.type());
const int diamter2 = diameter * diameter;
const int radius = diameter / 2;
const int radius2 = radius * radius;
const int rowmax = src.rows - radius;
const int colmax = src.cols - radius;
for (int r = radius; r < rowmax; r++) {
for (int c = radius; c < colmax; c++) {
uchar msk = mask.at<uchar>(r, c);
if (msk) {
Vec3b& srcP = src.at<Vec3b>(r, c);
Vec3b& dstP = dst.at<Vec3b>(r, c);
// It is treated as a grey image even though three channels
int sum0 = 0;
int count0 = 0;
for (int dy = -radius; dy <= radius; dy++) {
for (int dx = -radius; dx <= radius; dx++) {
if (mask.at<uchar>(r + dy, c + dx)) {
const Vec3b& pp = src.at<Vec3b>(r + dy, c + dx);
sum0 += pp[0];
count0++;
}
}
}
if (count0 > 4) {
const int avg0 = sum0 / count0;
// Blur
dstP[0] = avg0;
dstP[1] = avg0;
dstP[2] = avg0;
}
else {
dstP[0] = 0;
dstP[1] = 0;
dstP[2] = 0;
}
}
else {
Vec3b& dstP = dst.at<Vec3b>(r, c);
dstP[0] = 0;
dstP[1] = 0;
dstP[2] = 0;
}
}
}
}